AOP讲解and小demo

35 篇文章 0 订阅
1 篇文章 0 订阅

AOP

1.1 AOP理解

OOP (Object Oriented Programming) 面向对象编程

AOP (Aspect Oritented Programming) 面向切面编程)

两者不是替代的关系而是互补的,AOP主要就是在不改变原本代码功能的情况下去新增功能

在spring中AOP很重要,可以理解为,AB业务互不影响且要去实现C的业务,但是我们需要在执行A业务的时候,在C业务功能的基础上,让A业务完成想要的功能,让B业务完成B业务需要的更强大的功能,前提就是C业务是基础业务是不能被更改的,不然就要影响其他业务。

所以在这个时候,AOP就做出了一个动作,A业务在C业务的想要添加的方法前面砍断,添加一个新的A自己的业务上去后,再将重新组合好的业务给织入原来的C业务方法那里去,继续执行C业务后面的功能。B业务一样的。

1.2 AOP实现

1.2.1 注解实现
  1. 添加依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.18</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.6</version>
    <scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.18</version>
</dependency>

  1. 修改xml配置文件
<?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.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.xs">

    <!--    注解实现:-->

<!--    开启注解:注解是需要开启的-->
<!--    <context:annotation-config></context:annotation-config>-->
<!--    扫描注解:扫描注解的话,是辉自动开启注解的,所以上面开启注解语句就可以不要了-->
    <context:component-scan base-package="com.xiaowang">
    </context:component-scan>

<!--    加载配置文件到spring里面,这样就可以去获取/置文件中的数据了-->
<!--    classpath:就是加载resource类路径下面的配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--
    开启动态代理
    proxy-target-class:
    如果为true那就是类代理,也就是jdk实现动态代理
    如果是false那就是cglib实现动态代理
    默认是false
    -->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>

最主要的就是加上了:

xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xs

注意:使用AOP的话是需要开启动态代理的哈,proxy-target-class的值true代表jdk实现,false代表cglib实现

  1. 创建切面类
    创建切面类,将其放入spring中,我们这里来通过测试类中testJDK方法来演示,在UserServiceImpl中的public User login(String username, String password) { }这里进行切入:先看一下未切入之前的输出结果:
    在这里插入图片描述

开始创建切入类

//放入spring中
@Component
@Aspect //这个标签是标记这个类是一个切面
public class PetAspect {

    /**
     * 标记再哪里什么时候执行切入
     * @param joinPoint
     */


//    execution:里面写的就是切入点,切入点有很多种语法,下面这个是最精准的切入点,直接定位到了具体的方法,切入点的语法有很多,下面一一介绍
    @Before("execution(public com.xiaowang.entity.User com.xiaowang.PetService.impl.PetServiceImpl.login(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void before(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("before之前执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());
    }
}

执行结果:
在这里插入图片描述
我们可以看到,就在login方法的前面就添加进去了我们的对象的方法,我们拦截的东西也可以看到了,这里同时还有多种类似于@before的注解,其功能就是在这个方法切入点的哪一个位置进行切入,例如还有:

/**
     * 标记再哪里什么时候执行切入,
     * @befor 代表在标记位置的前面切入
     * @param joinPoint
     */

//    execution:里面写的就是切入点,切入点有很多种语法,下面这个是最精准的切入点,直接定位到了具体的方法,切入点的语法有很多,下面一一介绍
    @Before("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void before(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("before之前执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());
    }

    /**
     * @After 在切入点的后面切入方法功能进去
     * 相当于finally执行,就是必备执行
     * @param joinPoint
     */

    @After("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void after(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("after执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());
    }

    /**
     * @AfterReturning 相当于正常执行,就是拦截方法正确执行后就可以执行
     * @param joinPoint
     */
    @AfterReturning("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void afterReturning(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("afterReturning执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());
    }

    /**
     * 拦截的语句那里抛出异常后被执行
     * @param joinPoint
     */
    @AfterThrowing("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void afterThrowing(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("afterThrowing执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());
    }

①拦截方法正常执行的时候结果:
在这里插入图片描述
②拦截方法有异常的时候结果:
在这里插入图片描述

如果使用AOP去管理事务的话,那我们就可以在before里面写事务的开启,在afterReturning中提交事务,在afterThrowing中去回滚事务,

所以在@afterThrowing这里的话就可以再加一点属性,去设置哪些地方需要回滚,哪些地方不需要回滚

/**
     * 拦截的语句那里抛出异常后被执行,相当于catch
     * @param joinPoint
     */
    @AfterThrowing(value = "execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))",throwing = "e")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void afterThrowing(JoinPoint joinPoint,Exception e){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("afterThrowing执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());

        //事务
        System.out.println("抛出异常进行回滚"+e.getMessage());
    }

在这里插入图片描述
独特的环绕执行:

/**
     * 环绕执行,就像过滤器一样,再dofilt前面执行,后面执行
     * @param proceedingJoinPoint
     * @return
     */

    @Around(value = "execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public Object around(ProceedingJoinPoint proceedingJoinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("around之前执行");
        Object proceed = proceedingJoinPoint.proceed();//代理的方法执行
        System.out.println("around之后执行");
        return proceed;
    }
1.2.2 xml实现

使用xml实现当然前期的依赖呀、配置文件的头部那些基本的都是要有哈,和注解实现的前面一样,区别就在于对切面类的编写的时候,切入点不用写在注解上了,而是写在xml文件里了,也就是删掉or注释掉切面类中的注解呀


//放入spring中
@Component
@Aspect //这个标签是标记这个类是一个切面
public class PetAspect {

    /**
     * 标记再哪里什么时候执行切入,
     * @befor 代表在标记位置的前面切入
     * @param joinPoint
     */

//    execution:里面写的就是切入点,切入点有很多种语法,下面这个是最精准的切入点,直接定位到了具体的方法,切入点的语法有很多,下面一一介绍
  //  @Before("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void before(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("before之前执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());
    }

    /**
     * @After 在切入点的后面切入方法功能进去
     * 相当于finally执行,就是必备执行
     * @param joinPoint
     */

  //  @After("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void after(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("after执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());
    }

    /**
     * @AfterReturning 相当于正常执行,就是拦截方法正确执行后就可以执行
     * @param joinPoint
     */
  //  @AfterReturning("execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void afterReturning(JoinPoint joinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("afterReturning执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());
    }

    /**
     * 拦截的语句那里抛出异常后被执行,相当于catch
     * @param joinPoint
     */
  //  @AfterThrowing(value = "execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))",throwing = "e")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public void afterThrowing(JoinPoint joinPoint,Exception e){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("afterThrowing执行");
        System.out.println("拦截的参数为:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("拦截的方法为:"+joinPoint.getSignature().getDeclaringTypeName() + joinPoint.getSignature().getName());
        System.out.println("拦截的位置为:"+joinPoint.getStaticPart());
        System.out.println("代理对象为:"+joinPoint.getThis());
        System.out.println("目标对象为:"+joinPoint.getTarget());

        //事务
        System.out.println("抛出异常进行回滚"+e.getMessage());
    }

  /*  *//**
     * 环绕执行,就像过滤器一样,再dofilt前面执行,后面执行
     * @param proceedingJoinPoint
     * @return
     *//*

    //@Around(value = "execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))")
    //想将下面这个方法切入到测试类中testJDK()方法的前面执行
    public Object around(ProceedingJoinPoint proceedingJoinPoint){//JoinPoint参数是切入点的意思,具体的切入点通过before注解去填写就行
        //切入面中,是可以获取很多被拦截的东西,
        System.out.println("around之前执行");
        Object proceed = proceedingJoinPoint.proceed();//代理的方法执行
        System.out.println("around之后执行");
        return proceed;
    }
*/
}

注意:我们在切面类上面加了@Aspect 这个注解后,就代表着这是一个切面了,就不用在xml中去通过<aop:aspect ref=“userAspect”>去定义这个类未切面类,两种方法二选一即可。

xml配置文件:

<?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.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:annotation-config></context:annotation-config>-->
<!--    扫描注解:扫描注解的话,是辉自动开启注解的,所以上面开启注解语句就可以不要了-->
    <context:component-scan base-package="com.xiaowang"> </context:component-scan>

<!--    加载配置文件到spring里面,这样就可以去获取/置文件中的数据了-->
<!--    classpath:就是加载resource类路径下面的配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>

<!--    装配注解,也就是将各个注解进行关联起来-->

    <!--
    开启动态代理
    proxy-target-class:
    如果为true那就是类代理,也就是jdk实现动态代理
    如果是false那就是cglib实现动态代理
    默认是false
    -->
<aop:aspectj-autoproxy proxy-target-class="false"/>

<!--    使用xml去实现aop-->
    <aop:config>
<!--        定义切面信息,如果切面类上使用了@Aspect注解,就可以不用定义这个切面类信息,但是切面点啥的还是要配置-->
        <aop:aspect ref="petAspect">
<!--            定义切面点-->
            <aop:pointcut id="add" expression="execution(public com.xiaowang.entity.Pet com.xiaowang.dao.impl.PetDaoImpl.add(java.lang.String,java.lang.String))"/>
<!--            实现切面类的增强功能-->
            <aop:before method="before" pointcut-ref="add"/>
            <aop:after-returning method="afterReturning" pointcut-ref="add"/>
            <aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="add"/>
            <aop:after method="after" pointcut-ref="add"/>
        </aop:aspect>
    </aop:config>
</beans>
1.2.3 切入点语法
直接精准到一个方法上面去
execution( public com.zlt.entity.User com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
任意权限修饰符
execution(  com.zlt.entity.User com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
无返回类型
execution( void com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
有返回类型
execution( !void com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
任意返回类型
execution( * com.zlt.service.impl.UserServiceImpl.login(java.lang.String,java.lang.String))
任意参数
execution( * com.zlt.service.impl.UserServiceImpl.login(..))
类中的任意方法
execution( * com.zlt.service.impl.UserServiceImpl.*(..))
类中以指定内容开头的方法
execution( * com.zlt.service.impl.UserServiceImpl.select*(..))
包中的任意类的任意方法不包含子包下面的类
execution( * com.zlt.service.impl.*.*(..))
包中及其下的任意类的任意方法
execution( * com.zlt.service..*.*(..))

1.3 AOP应用场景

  1. 事务管理的时候(before里开启事务、afterThowning回滚事务,after提交事务)
  2. 获取某个功能的执行时间(before设置开始时间、after设置结束时间)
  3. …等等
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值