Spring AOP 实战案例

什么是AOP?

AOP (Aspect Orient Programming)是面向切面变成,用通俗的话说,就是面向多个对象的编程。把需要的公共方法提取出来,组成一个切面,在指定的切点上进行方法拦截。spring会利用动态代理的方式生成代理东西,执行加入公共方法和目标方法。

相关概念

Aspect(切面):是通知和切入点的结合,通知和切入点共同定义了关于切面的全部内容—它的功能、在何时和何地完成其功能

joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.

Pointcut(切入点):所谓切入点是指我们要对哪些joinpoint进行拦截的定义.

通知定义了切面的”什么”和”何时”,切入点就定义了”何地”.

Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Target(目标对象):代理的目标对象

Weaving(织入):是指把切面应用到目标对象来创建新的代理对象的过程.切面在指定的连接点织入到目标对象

定义service

package com.demo.test.aop;
import com.demo.test.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestAopController {
    private static Logger logger = LoggerFactory.getLogger(TestAopController.class);
    @RequestMapping("/testAop")
    public String getuser(@RequestParam String id) {
        logger.info("【执行实际方法】获取id: {}",id );
        int age =  Integer.valueOf(id); // 可能出现异常的地方,出发异常通知

        rturn "age=>"+age;
    }

}

定义切面

package com.demo.test.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.ComponentScan;
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.Arrays;


@Aspect
@Component
public class LogAspects {
    private static Logger log = LoggerFactory.getLogger(LogAspects.class);
/**
     * 定义切入点,切入点为com.demo.test.aop下的所有函数
     */
    @Pointcut("execution(* com.demo.test.aop..*.*(..))")
    private void webLog(){

    }


    /**
     * 前置通知
     * 在目标方法执行之前执行执行的通知。
     *
     * 前置通知方法,可以没有参数,也可以额外接收一个JoinPoint,Spring会自动将该对象传入,代表当前的连接点,通过该对象可以获取目标对象 和 目标方法相关的信息。
     *
     * 注意,如果接收JoinPoint,必须保证其为方法的第一个参数,否则报错。
     * @param joinPoint
     */
    @Before(value = "webLog()")
    public void methodBefore(JoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //打印请求内容
        log.info("========================【前置通知】请求内容开始======================");
        log.info("请求地址:" + request.getRequestURI().toString());
        log.info("请求方式" + request.getMethod());
        log.info("请求类方法" + joinPoint.getSignature());
        log.info("请求类方法参数" + Arrays.toString(joinPoint.getArgs()));
        log.info("========================【前置通知】请求内容结束======================");

    }

    /**
     * 后置通知
     * 在目标方法执行之后执行的通知。
     * 在后置通知中也可以选择性的接收一个JoinPoint来获取连接点的额外信息,但是这个参数必须处在参数列表的第一个。
     *  后置通知,方法执行报错,执行不下去了,就不会执行了
     * @param o
     */
    @AfterReturning(returning = "o", pointcut = "webLog()")
    //这个注解的作用是:在切入点,return后执行,如果想对某些方法的返回参数进行处理,可以在这操作
    public void methodAfterReturing(Object o) {
        log.info("--------------【AfterReturning - 后置通知】返回内容----------------");
        log.info("Response内容:" + o);
        log.info("--------------【AfterReturning - 后置通知】返回内容----------------");

    }


    /**
     * 是在目标方法执行之后执行的通知,实际是代理方法执行完毕后执行。
     * 最终通知,无论被代理的方法是否被执行,都会执行这个方法,这个方法就是在新生成的代理类执行完后执行
     * 和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返-例如抛出异常,则后置通知不会执行。
     * 而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。
     *
     * 另外,后置通知可以通过配置得到返回值,而最终通知无法得到。
     *
     * 最终通知也可以额外接收一个JoinPoint参数,来获取目标对象和目标方法相关信息,但一定要保证必须是第一个参数。
     * @param joinpoint
     */
    @After(value = "webLog()")
    public void logEnd(JoinPoint joinpoint) {

        log.info("--------------【After - 最终通知】----------------");
    }

    /**
     * 在目标方法抛出异常时执行的通知
     *
     * 可以配置传入JoinPoint获取目标对象和目标方法相关信息,但必须处在参数列表第一位
     *
     * 另外,还可以配置参数,让异常通知可以接收到目标方法抛出的异常对象。
     * @param object
     */

    @AfterThrowing(value = "webLog()",throwing = "object")
    public void logEnd(Exception  object) {

        log.info("--------------【异常通知】---------------==》" + object);
    }

    /**
     * 2.环绕通知
     * 在目标方法执行之前和之后都可以执行额外代码的通知。
     *
     * 在环绕通知中必须显式的调用目标方法,目标方法才会执行,这个显式调用时通过ProceedingJoinPoint来实现的,可以在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,注意这个参数必须处在环绕通知的第一个形参位置。
     *
     * **要注意,只有环绕通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint。
     *
     * 环绕通知需要返回返回值,否则真正调用者将拿不到返回值,只能得到一个null。
     *
     * 环绕通知有控制目标方法是否执行、有控制是否返回值、有改变返回值的能力。
     *
     * 环绕通知虽然有这样的能力,但一定要慎用,不是技术上不可行,而是要小心不要破坏了软件分层的“高内聚 低耦合”的目标。
     * @param pjp
     * @return
     */

    @Around("webLog()")
    public Object arroundFunc(ProceedingJoinPoint pjp) {
        try {
            Object result = null;
            Object[] args = pjp.getArgs();
            beforeFunc();//前置通知
            result = pjp.proceed(args);
            afterReturnFunc();//后置通知
            return result;
        } catch (Throwable t) {
            afterThrowFunc();//异常通知
            throw new RuntimeException(t);
        } finally {
            afterFunc();//最终通知
        }
    }

    void beforeFunc(){
        log.info("--------------【环绕通知-前置通知】---------------==》");
    }

    void afterReturnFunc(){
        log.info("--------------【环绕通知-后置通知】---------------==》");
    }

    public void afterThrowFunc() {
        log.info("--------------【环绕通知-异常通知】---------------==》");
    }

    public void afterFunc() {
        log.info("--------------【环绕通知-最终通知】---------------==》");
    }
}

模拟正常访问

浏览器中访问: http://localhost:8082/testAop?id=11
此时没有异常,正常结果

执行结果如下:
在这里插入图片描述

模拟异常访问

http://localhost:8082/testAop?id=ooo
参数id=ooo,不能转为整形,报异常
执行结果如下

在这里插入图片描述

执行顺序

Spring版本不一样,通知执行顺序可能也会存在差异
下面以Spring4.0版本、Spring5.28版本进行测试

Spring4.0

正常情况:环绕前置=====@Before目标方法执行=环绕返回=环绕最终===@After=====@AfterReturning
    异常情况:环绕前置=====@Before目标方法执行=环绕异常=环绕最终===@After=====@AfterThrowing

Spring5.28

正常情况:环绕前置=====@Before=目标方法执行=@AfterReturning=====@After=环绕返回=环绕最终
    异常情况:环绕前置=====@Before=目标方法执行=@AfterThrowing=====@After=环绕异常=环绕最终

这里没有继承接口,aop是如何利用动态代理的?

1.默认使用 JDK 动态代理,这样便可以代理所有的接口类型(interface)

2.Spring AOP也支持CGLIB的代理方式。如果我们被代理对象没有实现任何接口或者实现的接口都是空接口,则是CGLIB

3.我们可以强制使用CGLIB,指定proxy-target-class = “true” 或者 基于注解@EnableAspectJAutoProxy(proxyTargetClass = true)

切入点表达式

定义切入点的时候需要一个包含名字和任意参数的签名,还有一个切入点表达式,如execution(public * com.example.aop…(…))

切入点表达式的格式:execution([可见性]返回类型[声明类型].方法名(参数)[异常])
其中[]内的是可选的,其它的还支持通配符的使用:

  1. *:匹配所有字符
  2. …:一般用于匹配多个包,多个参数
  3. +:表示类及其子类
    4)运算符有:&&,||,!

切入点表达式关键词用例:
1)execution:用于匹配子表达式。
//匹配com.cjm.model包及其子包中所有类中的所有方法,返回类型任意,方法参数任意
@Pointcut(“execution(* com.cjm.model…(…))”)
public void before(){}

execution(* com.test.aop…*.getuser(…)) 表示指定方法getuser

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EmineWang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值