Spring使用注解实现AOP

一、AspectJ概述

AspectJ是一个面向切面的框架,它扩展了Java语言、定义了AOP语法,能够在编译期提供代码的织入,它提供了一个专门的编译期用来生成遵守字节编码规范的Class文件。

@Aspect是AspectJ 5新增的功能,使用JDK 5.0注解技术和正规的AspectJ切点表达式语言描述切面。因此在使用@Aspect之前,需要保证所使用的JDK是5.0或更高版本,否则将无法使用注解技术。

Spring通过集成AspectJ实现了以注解的方式定义切面,大大减轻了配置文件的工作量。此外,因为Java的反射机制无法获取方法参数名,Spring还需要利用轻量级的字节码处理框架asm(已集成在Spring Core模块中)处理@AspectJ中所描述的方法参数名。

二、@Aspect(定义切面)、@Before(前置增强)、@AfterReturning(后置增强)注解配置切面

1、使用注解定义切面以实现日志切面功能,如下

package edu.cn.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

@Aspect
public class UserServiceLogger {
    private static final Logger log = LoggerFactory.getLogger(UserServiceLogger.class);

    @Before("execution(* edu.cn.service.UserService.*(..))")
    public void before(JoinPoint jp){
        log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName()
                + "方法。方法入参:" + Arrays.toString(jp.getArgs()));
    }

    @AfterReturning(pointcut = "execution(* edu.cn.service.UserService.*(..))", returning = "returnValue")
    public void afterReturning(JoinPoint jp, Object returnValue){
        log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName()
                + "方法。方法返回值:" + returnValue);
    }

} 

@Aspect等注解在aspectjweaver依赖下。在上述代码中,使用@Aspect注解将UserServiceLogger定义为切面,并且使用@Before注解将before()方法定义为前置增强,使用@AfterReturning方法将afterReturning()方法定于为后置增强

为了能够获得当前连接点的信息,在增强方法中添加了JoinPoint类型的参数,Spring会自动注入该实例。

对于后置增强,还可以定义一个参数用于接受目标方法的返回值。需要注意的是,必须在@AfterReturning注解中通过returning属性指定该参数的名称,Spring会将目标方法的返回值赋值给指定名称的参数。

1.1、对于相同的切入点要求,可以统一定义,以便于重用和维护,如下

package edu.cn.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

@Aspect
public class UserServiceLogger {
    private static final Logger log = LoggerFactory.getLogger(UserServiceLogger.class);
    
    @Pointcut("execution(* edu.cn.service.UserService.*(..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void before(JoinPoint jp){
        log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName()
                + "方法。方法入参:" + Arrays.toString(jp.getArgs()));
    }

    @AfterReturning(pointcut = "pointcut()", returning = "returnValue")
    public void afterReturning(JoinPoint jp, Object returnValue){
        log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName()
                + "方法。方法返回值:" + returnValue);
    }

}

切入点表达式可以使用@Pointcut注解来表示,而切入点签名则需要通过一个普通的方法定义来提供,如上述代码中的pointcut()方法,作为切入点签名的方法必须返回void类型。定义好切入点后,就可以使用“pointcut()”签名进行引用

2、切面定义完后,还需要在Spring配置文件中完成织入工作,如下

只需在配置文件中添加<aop:aspectj-autoproxy/>元素,就可以启用对于@AspectJ注解的支持,Spring将自动为匹配的Bean创建代理

为了注册定义好的切面,还需在Spring配置文件中声明UserServiceLogger的一个实例。如果不需要被其他Bean引用,可以不指定id属性

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd 
            http://www.springframework.org/schema/aop 
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="service,dao"/>
    <bean class="edu.cn.service.UserServiceLogger"></bean>
    <aop:aspectj-autoproxy/>
</beans>

三、@AfterThrowing(异常抛出增强)、@After(最终增强)、@Around(环绕增强)注解进行增强的配置

1、@AfterThrowing(异常抛出增强)

使用@AfterThrowing注解可以定义异常抛出增强。如果需要获取抛出的异常,可以为增强方法声明相关类型的参数,并通过@AfterThrowing注解的throwing属性指定该参数名称,Spring会为其注入从目标方法抛出的异常实例,如下

package edu.cn.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class ErrorLogger {
    private static final Logger log = LoggerFactory.getLogger(ErrorLogger.class);

    @AfterThrowing(pointcut = "execution(* edu.cn.service.UserService.*(..))", throwing = "e")
    public void afterThrowing(JoinPoint jp, RuntimeException e){
        log.error(jp.getSignature().getName() + "方法方法异常:" + e);
    }
}

2、@After(最终增强)

使用@After注解可以定义最终增强

package edu.cn.service;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class AfterLogger {
    private static final Logger log = LoggerFactory.getLogger(AfterLogger.class);
    
    @After("execution(* edu.cn.service.UserService.*(..))")
    public void afterLogger(JoinPoint jp){
        log.info(jp.getSignature().getName() + "方法结束执行。");
    }
}

3、@Around(环绕增强)

使用@Around注解可以定义环绕增强。通过为增强方法声明ProceedingJoinPoint类型的参数,可以获得连接点信息。通过它的proceed()方法可以调用真正的目标方法,从而实现对连接点的完全控制。

package edu.cn.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;

@Aspect
public class AroundLogger {
    private static final Logger log = LoggerFactory.getLogger(AroundLogger.class);
    
    @Around("execution(* edu.cn.service.UserService.*(..))")
    public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable {
        log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName() 
                + "方法。方法入参:" + Arrays.toString(jp.getArgs()));
        try {
            Object result = jp.proceed();
            log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName()
            + "方法。方法返回值:" + result);
        } catch (Throwable e) {
            log.error(jp.getSignature().getName() + "方法发生异常:" + e);
            throw e;
        }finally {
            log.info(jp.getSignature().getName() + "方法结束执行。");
        }
    }
}

相关推荐