Spring AOP面向切面编程入门

本文详细解析了AOP(面向切面编程)中各种通知类型的功能与应用,包括前置通知、后置通知、返回通知、异常通知和环绕通知。通过具体实例展示了如何使用这些通知在目标方法执行前后进行增强,实现日志记录、性能监控等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前置通知
如何声明前置通知:在切面的的一个方法上面使用@Before("切入点表达式")声明。
前置通知何时执行:在目标方法执行之前执行。目标方法:切入点表达式中指定的方法。
AspectJ 切入点表达式的编写:
execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))
第一个 *表示任意修饰符任意返回值。
第二个 *表示 com.sqp.spring.aop.dao.MyCalculator类中任意方法。
(..)表示匹配任意数量的参数。
可以在通知方法中声明一个JoinPoint 类型的参数,然后就可以访问连接细节,如方法名,参数。
举个例子:

/**
 * @Component: 把该放入Spring IOC容器中
 * @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
 */
@Aspect
@Component
public class loggingAspect
{
    /**
     * 前置通知: 在目标方法执行之前执行前置通知(使用execution()指定目标方法)
     */
    @Before("execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))")
    public void beforeAdvice(JoinPoint joinPoint)
    {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //获取入参
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("The method [" + methodName + "] input params is " + args);
    }
}


二、后置通知
如何声明后置通知:在切面的一个方法上使用@After注解("切入点表达式")声明。
后置通知何时执行:在目标方法执行之后执行后置通知(无论是否出现异常)。
后置通知中不能访问目标方法的返回结果,因为执行方法可能会抛出异常。
举个例子:

/**
 * @Component: 把该放入Spring IOC容器中
 * @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
 */
@Aspect
@Component
public class loggingAspect
{
    /**
    * 后置通知: 在目标方法执行之后执行后置通知(无论是否出现异常)。
    * 在后置通知中不能访问目标方法的执行结果,因为方法执行可能会出异常。
    */
    @After("execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))")
    public void afterAdvice(JoinPoint joinPoint)
    {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method [" + methodName + "] ends.");
    }
}



三、返回通知
返回通知如何声明:在切面的一个方法上使用 @AfterReturning("切入点表达式")注解声明。
返回通知何时执行:目标方法正常结束后执行返回通知(不出现异常)。
在返回中可以访问目标方法的执行结果:
① 在@AfterReturning注解中添加returning属性。
② 原始的切入点表达式可以声明在value属性中。
③ 在返回通知方法的参数列表中添加一个新的参数(可声明为Object类型),参数名必须与returning属性的值一致。
举个例子:

/**
 * @Component: 把该放入Spring IOC容器中
 * @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
 */
@Aspect
@Component
public class loggingAspect
{
    /**
     * 返回通知: 在目标方法执行之后执行后置通知(无论是否出现异常)。
     */
    @AfterReturning(value = "execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))",
        returning = "result")
    public void returnAdvice(JoinPoint joinPoint, Object result)
    {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method [" + methodName + "] output param is " + result);
    }
}



四、异常通知
如何声明异常通知:在切面的一个方法中使用 @AfterThrowing("切入点表达式")注解声明。
异常通知何时执行:异常通知在目标方法抛出异常时执行。
如何声明访问目标方法抛出的异常:
① 在@AfterThrowing注解中添加 throwing属性。
② 原始的切入点表达式可以可以声明在value属性中。
在异常通知方法的参数列表中增加一个新的(异常类型的)参数,其名称必须与throwing的值一致。
③ 如果只需要在抛出某种特定异常时,才执行异常通知:
可以将参数声明为特定异常类型,如:NullPointerException。
举个例子:
/**
 * @Component: 把该放入Spring IOC容器中
 * @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
 */
@Aspect
@Component
public class loggingAspect
{
    /**
     * 异常通知: 在目标方法执行之后执行后置通知(无论是否出现异常)。
     */
    @@AfterThrowing(value = "execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))",
        throwing= "ex")
    public void returnAdvice(JoinPoint joinPoint, Exception ex)
    {
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method [" + methodName + "] occurs exception: " + ex);
    }
}

五、环绕通知
环绕通知是所有通知类型中功能最强大的(但不意味着是最常用的),能够全面地控制目标方法(连接点),甚至可以决定是否执行目标方法(连接点)。
环绕通知方法的参数类型必须为 ProceedingJoinPoint,它是 JoinPoint 的子接口,可以决定目标方法是否执行,何时执行。
在环绕通知中,需要明确调用 ProceedingJoinPoint 的 proceed()方法来执行被代理的(目标)方法,如果没有调用 proceed() 方法,就会导致,通知执行了,目标方法没有执行。
环绕通知方法需要有返回值,且返回值必须是执行 proceed() 的结果,否则会抛出空指针异常。
举个例子:
 

/**
 * @Component: 把该放入Spring IOC容器中
 * @Aspect: 声明该类为一个切面(需要先放入Spring IOC容器中)
 */
@Aspect
@Component
public class loggingAspect
{
    @Around("execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint)
    {
        Object result = null;
        String methodName = proceedingJoinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(proceedingJoinPoint.getArgs());
        try
        {
            //前置通知
            System.out.println("The method [" + methodName + "] input params is " + args);
            //执行目标方法
            result = proceedingJoinPoint.proceed();
            //返回通知
            System.out.println("The method [" + methodName + "] output param is " + result);
        } catch (Throwable e)
        {
            //异常通知
            System.out.println("The method [" + methodName + "] occurs exception: " + e);
        }
        //后置通知
        System.out.println("The method [" + methodName + "] ends.");
        return result;
    }
}

最后,曾经项目中编写的用于拦截controller的请求信息,并将请求信息输入到日志文件中的案例

package com.xxx.aspact.log;

import org.aopalliance.intercept.Joinpoint;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

/**
 * 拦截日志的切面
 */
@Aspect
@Component
public class LogAspace {

    private static  Logger logger = Logger.getLogger(Logger.class);
    /**
     * 切入点,拦截范围
     * 第一个*表示任何返回类型的方法
     * 第二个*表示任意类
     * 第三个*表示任意方法,(..)表示任意参数的方法
     */
    @Pointcut("execution(public * com.rzx.demo.controller..*.*(..))")
    public void webLog(){}

    /**
     * 配置一个前置通知,在目标方法执行前执行
     * 如何声明前置通知:在切面的的一个方法上面使用@Before("切入点表达式")声明。
     * 前置通知何时执行:在目标方法执行之前执行。目标方法:切入点表达式中指定的方法。
     * AspectJ 切入点表达式的编写:
     * execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))
     * 第一个 *表示任意修饰符任意返回值。
     * 第二个 *表示 com.sqp.spring.aop.dao.MyCalculator类中任意方法。
     * (..)表示匹配任意数量的参数。
     * 可以在通知方法中声明一个JoinPoint 类型的参数,然后就可以访问连接细节,如方法名,参数。
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint){
        //如果不在controller中可以通过使用该api获取request和response
        ServletRequestAttributes servletRequestAttributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //获取request
        HttpServletRequest request = servletRequestAttributes.getRequest();

        logger.info("----------request------url----------"+request.getRequestURL());
        logger.info("----------request-------mothod------"+request.getMethod());
        logger.info("----------request--------ip----------"+request.getRemoteAddr()+":"+request.getRemotePort());

        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()){
            String key = parameterNames.nextElement();
            String value = request.getParameter(key);
            logger.info("request------param--------"+key+":"+value);
        }

    }

    /**
     * 无论是否异常,当目标方法执行完毕都会调用
     * 如何声明后置通知:在切面的一个方法上使用@After注解("切入点表达式")声明。
     * 后置通知何时执行:在目标方法执行之后执行后置通知(无论是否出现异常)。
     * 后置通知中不能访问目标方法的返回结果,因为执行方法可能会抛出异常。
     * @param joinPoint
     */
    @After("webLog()")
    public void doAffter(JoinPoint joinPoint){

    }
    /**
     * 返回通知,只有目标方法正常执行完毕(不出现异常)后执行
     * 方法的参数就是目标方法的返回值,名称要和returning的值一样
     * 返回通知如何声明:在切面的一个方法上使用 @AfterReturning("切入点表达式")注解声明。
     * 返回通知何时执行:目标方法正常结束后执行返回通知(不出现异常)。
     * 在返回中可以访问目标方法的执行结果:
     * ① 在@AfterReturning注解中添加returning属性。
     * ② 原始的切入点表达式可以声明在value属性中。
     * ③ 在返回通知方法的参数列表中添加一个新的参数(可声明为Object类型),参数名必须与returning属性的值一致。
     */
    @AfterReturning(returning = "ret",pointcut = "webLog()")
    public void AfterReturning(JoinPoint joinPoint,Object ret){

    }

    /**
     * 异常通知
     * 如何声明异常通知:在切面的一个方法中使用 @AfterThrowing("切入点表达式")注解声明。
     * 异常通知何时执行:异常通知在目标方法抛出异常时执行。
     * 如何声明访问目标方法抛出的异常:
     * ① 在@AfterThrowing注解中添加 throwing属性。
     * ② 原始的切入点表达式可以可以声明在value属性中。
     * 在异常通知方法的参数列表中增加一个新的(异常类型的)参数,其名称必须与throwing的值一致。
     * ③ 如果只需要在抛出某种特定异常时,才执行异常通知:
     * 可以将参数声明为特定异常类型,如:NullPointerException。
     */
    @AfterThrowing(pointcut = "webLog()",throwing = "ex")
    public void doAfterThrowing(JoinPoint joinPoint,Exception ex){

    }

    /**
     * 环绕通知,可以控制目标方法的执行时机,调用目标方法前和目标方法后(不出现异常)
     * 都会回调改方法,方法中必须手动指定执行目标方法process():
     * 必须有返回值,否则目标方法正常调用完毕出现空指针
     * 1.环绕通知是所有通知类型中功能最强大的(但不意味着是最常用的),能够全面地控制目标方法(连接点),甚至可以决定是否执行目标方法(连接点)。
     * 2.环绕通知方法的参数类型必须为 ProceedingJoinPoint,它是 JoinPoint 的子接口,可以决定目标方法是否执行,何时执行。
     * 3.在环绕通知中,需要明确调用 ProceedingJoinPoint 的 proceed()方法来执行被代理的(目标)方法,如果没有调用 proceed() 方法,就会导致,通知执行了,目标方法没有执行。
     * 4.环绕通知方法需要有返回值,且返回值必须是执行 proceed() 的结果,否则会抛出空指针异常。
     * @param proceedingJoinPoint
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint){
        //手动调用目标方法
        try {
            return  proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值