Spring——AOP的5大通知注解使用

目录

1.使用AOP通知注解的步骤

1.1 pom.xml文件中加入maven依赖

 1.2 在Spring配置文件中声明AspectJ的自动代理生成器

2.AOP中的5大通知注解

2.1 @Before:前置通知

2.2 @AfterReturning:后置通知

 2.3 @Around:环绕通知

 2.4 @AfterThrowing:异常通知

2.5 @After:最终通知

 3. @Pointcut 定义切入点


1.使用AOP通知注解的步骤

使用apsectj框架的注解,实现前置通知,步骤如下:
1.新建Maven项目

2.修改pom.xml,加入依赖
    spring-context依赖、spring-aspects依赖、junit

3.创建业务接口和实现类

4.创建一个切面类(普通类)
    1) 在类的上面加入@Aspect
    2) 在类中定义方法,方法表示切面的功能。在方法的上面加入AspectJ框架中的通知注解
       例如:@Before(value="切入点表达式")

5.创建spring配置文件
    1) 声明目标对象
    2) 声明切面类对象
    3) 声明自动代理生成器

6.创建测试类,测试目标方法执行时,增加切面的功能

1.1 pom.xml文件中加入maven依赖

1. spring依赖 

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

2. spring-aspects依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

3.单元测试依赖 (可有可没有主要是测试用的)

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>

 1.2 在Spring配置文件中声明AspectJ的自动代理生成器

<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--  声明自动代理生成器::目的是创建目标对象的代理(也就是将两个bean结合起来)
 调用aspectj框架中的功能,寻找spring容器中的所有目标对象。把每个目标对象加入切面类中的功能
 ,生成代理,这个代理对象是修改内存中的目标对象,这个目标对象就是代理对象-->
    <aop:aspectj-autoproxy />
</beans>

2.AOP中的5大通知注解

2.1 @Before:前置通知

 /*
    前置通知方法的定义
    1.方法是public  2.方法是void   3.方法名称自定义
    4.方法可以有参数,如果有是joinpoint 也可以没有
     */
    /**
     * @Aspect: 切面类的注解。位置:放到某个类的上面 。作用::表示当前类是切面类
     *
     * @Before:前置通知
     * 属性:value切入点表达式,表示切面的执行位置在这个方法时,会同时执行切面的功能。
     * 位置:方法上面
     * 特点:1.执行时间:在目标方法之前执行的,
     *      2.不会影响目标方法的执行
     *      3.不会修改目标方法的执行结果。
     * 切面类中的通知方法,可以有参数
     * joinPoint必须是它。表示正在执行的业务方法。相当于反射中的Method
     * 使用要求:必须是参数列表的第一个。
     * 作用:获取方法执行时的信息,例如方法名称,方法的参数集合
     */

service包的接口类:

package com.liuhaiyang.service;
public interface SomeService {
    void doSome(String name,Integer age);

    void doOther();

}

同级目录下Impl包实现类

package com.liuhaiyang.service.Impl;
import com.liuhaiyang.service.SomeService;

public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        System.out.println("业务方法dosome(),"+name+"创建商品订单,"+age);

    }

    @Override
    public void doOther() {

        System.out.println("doOther方法执行了============");
        System.out.println("\r\n");
    }
}

通过前置通知方式完善功能

package com.liuhaiyang.handle;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
 *@Aspect: 切面类的注解。位置:放到某个类的上面 。作用::表示当前类是切面类
 */
@Aspect  //该注解表示这个类是切面类
public class MyAspect {
    /*
    前置通知方法的定义
    1.方法是public  2.方法是void   3.方法名称自定义
    4.方法可以有参数,如果有是joinpoint 也可以没有
     */
    /**
     * @Before:前置通知
     * 属性:value切入点表达式,表示切面的执行位置在这个方法时,会同时执行切面的功能。
     * 位置:方法上面
     * 特点:1.执行时间:在目标方法之前执行的,
     *      2.不会影响目标方法的执行
     *      3.不会修改目标方法的执行结果。
     */
 //   @Before(value ="execution(public void com.liuhaiyang.service.Impl.SomeServiceImpl.doSome(String ,Integer))")
  //  @Before(value ="execution(* *..SomeServiceImpl.doSome(..))")   //dosome方法的任意参数都可以加入切片功能
    @Before(value ="execution(* *..SomeServiceImpl.do*(..))")   //以do开头的所有方法的任意参数都可以加入切片功能
    /*
    可以在写一个@Before但是需要新写一个方法。且两个Before的先后顺不能确定
     */
    /**
     *切面类中的通知方法,可以有参数
     * joinPoint必须是它。表示正在执行的业务方法。相当于反射中的Method
     * 使用要求:必须是参数列表的第一个。
     * 作用:获取方法执行时的信息,例如方法名称,方法的参数集合
     */
    public void myBefore(JoinPoint jp){
        //获取方法的定义
        System.out.println("前置通知中,获取目标方法的定义:" +jp.getSignature());
        //结果是:前置通知中,获取目标方法的定义:void com.liuhaiyang.service.SomeService.doOther()
        System.out.println("前置通知中,获取方法名称="+jp.getSignature().getName());

        //获取方法执行时参数
        Object[] obj=jp.getArgs();
        int[] a={};


        for(Object ob:obj) {System.out.println("参数有:"+ob);}
        String getname=jp.getSignature().getName();
        if("doSome".equals(getname)){
            //切面代码
            System.out.println(getname+"前置通知,切面功能,在目标方法之前先执行:"+new Date());
        } else if("doOther".equals(getname)){
            System.out.println(getname+"前置通知,切面功能,在目标方法之前先执行:"+new Date());
        }


    }
}

在配置文件application.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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--        声明目标对象-->
    <bean id="someservice" class="com.liuhaiyang.service.Impl.SomeServiceImpl"/>

<!--    声明切面类对象-->
    <bean id="myaspect" class="com.liuhaiyang.handle.MyAspect"/>
<!--  声明自动代理生成器::目的是创建目标对象的代理(也就是将两个bean结合起来)
 调用aspectj框架中的功能,寻找spring容器中的所有目标对象。把每个目标对象加入切面类中的功能
 ,生成代理,这个代理对象是修改内存中的目标对象,这个目标对象就是代理对象-->
    <aop:aspectj-autoproxy />
</beans>

测试类

@Test
    public void test01(){
        String conf="application.xml";
        ApplicationContext ctx= new ClassPathXmlApplicationContext(conf);
        //加入代理的处理
        //1.目标方法执行时,有切面功能。
        // 2.service对象是改变后的代理对象com.sun.proxy.$Proxy10
        SomeService someservice=(SomeService) ctx.getBean("someservice");
        System.out.println(someservice.getClass().getName());
        //这里的someservice是代理对象,不在是SomeService
        someservice.doSome("张三",22);

    }

结果:

 看一下doOther是否能正常执行,执行结果是什么

测试类

 @Test
    public void test02(){
        String conf="application.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(conf);
        SomeService someservice=(SomeService) ctx.getBean("someservice");
        someservice.doOther();
        someservice.doSome("周爽",25);
    }

结果截图:

2.2 @AfterReturning:后置通知

/*
后置通知方法的定义
1.方法是public  2.方法是void   3.方法名称自定义
4.方法有参数,推荐使用Object类型
*/
/** @AfterReturning:后置通知
 * 属性:value切入点表达式
 *      returning自定义的变量,表示目标方法的返回值的
 *               自定义变量名称必须和通知方法的形参名一样。
 *  位置:在方法的上面
 *  特点:1.在目标方法之后,执行的。
 *       2.能获取到目标方法的执行结果。
 *       3.不会影响目标方法的执行
 *方法的参数:
 * Object res: 表示目标方法的返回值,使用res接受doOther的调用结果。
 * Object res = doOther();
 *
 * 后置通知的执行顺序
 * Object res=someserviceImpl.doOther(..);
 * myAfterReturning(res);
 */

service接口

package com.liuhaiyang.service;

import com.liuhaiyang.Stduent;

public interface SomeService {
    void doSome(String name,Integer age);

    Stduent doOther(String name, Integer age);

}

实现类:

package com.liuhaiyang.service.Impl;

import com.liuhaiyang.Stduent;
import com.liuhaiyang.service.SomeService;


public class SomeServiceImpl implements SomeService {

    @Override
    public void doSome(String name, Integer age) {
        System.out.println("\r\n");
        System.out.println("业务方法dosome(),"+name+"创建商品订单,"+age);
    }
    @Override
    public Stduent doOther(String name, Integer age) {
        Stduent student=new Stduent();
        System.out.println("执行业务方法doOther,"+name+"处理库存的"+age);
        student.setName(name);
        student.setAge(age);
        return student;
    }
}

完善的功能

package com.liuhaiyang.handle;

import com.liuhaiyang.Stduent;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect  //该注解表示这个类是切面类
public class MyAspect {
  
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
            returning="res")
    public void myAfterReturning(JoinPoint jp ,Object res){ // JoinPoint必须在Object前面
        System.out.println(res.getClass().getName());
        if(res!=null){
            ((Stduent) res).setName("liuhaiyang");
            ((Stduent) res).setAge(23);
        }
        System.out.println("后置通知,能拿到返回的结果+"+res);
   
    }
}

配置文件(代码和上面的配置文件的差不多没写,写点核心的):

<!--        声明目标对象-->
    <bean id="someservice" class="com.liuhaiyang.service.Impl.SomeServiceImpl"/>

<!--    声明切面类对象-->
    <bean id="myaspect" class="com.liuhaiyang.handle.MyAspect"/>
<!--  声明自动代理生成器::目的是创建目标对象的代理(也就是将两个bean结合起来)
 调用aspectj框架中的功能,寻找spring容器中的所有目标对象。把每个目标对象加入切面类中的功能
 ,生成代理,这个代理对象是修改内存中的目标对象,这个目标对象就是代理对象-->
    <aop:aspectj-autoproxy />

测试类:

 @Test
    public void test01(){
        String conf="application.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(conf);
        SomeService someservice=(SomeService) ctx.getBean("someservice");
        Stduent res=someservice.doOther("瓦达",30);
        System.out.println(res);
        someservice.doSome("周爽",25);
    }

结果截图:

 2.3 @Around:环绕通知

/*
    环绕通知方法的定义
    1.方法是public  2.方法必须有返回值, 推荐使用Object类型   3.方法名称自定义
    4.方法必须有ProceedingJoinPoint参数;
    */
    /**
     * @Around: 环绕通知
     * 属性: value切入点表达式
     * 位置:在方法定义的上面
     * 返回值:Object,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
     * 参数: ProceedingJoinPoint,相当于反射中的Method。
     *  作用:执行目标方法的,等于Maethod.invoke()
     *  ProceedingJoinPoint继承了JoinPoint.
     *  特点:1.在目标方法前和后都能增强功能
     *      2.控制目标方法是否执行。
     *      3.修改目标方法的执行结果。
     */

service接口:

package com.liuhaiyang.service;

public interface SomeService {
    void doSome(String name,Integer age);

    String doOther(String name, Integer age);

    String doFirst(String name);

}

实现类:

package com.liuhaiyang.service.Impl;

import com.liuhaiyang.service.SomeService;


public class SomeServiceImpl implements SomeService {

    @Override
    public void doSome(String name, Integer age) {
        System.out.println("\r\n");
        System.out.println("业务方法dosome(),"+name+"创建商品订单,"+age);
    }
    @Override
    public String doOther(String name, Integer age) {
        System.out.println("执行业务方法doOther,"+name+"处理库存的"+age);
        return "student";
    }

    @Override
    public String doFirst(String name) {
        System.out.println(name+"执行了业务方法doFirst,处理库存");
        return "abcd";
    }
}

通过环绕通知增强功能:

package com.liuhaiyang.handle;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect  //该注解表示这个类是切面类
public class MyAspect {

    @Around("execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myArroud(ProceedingJoinPoint pjp) throws Throwable {
        //获取方法执行时的参数值
        String name="";
        Object args[]= pjp.getArgs();
        if (args!=null & args.length>0){
            Object arg=args[0];
            if (arg!=null){
                name=(String) arg;
            }
        }

        for (Object arg:args){  //查看doFirst所有的参数
            System.out.println(arg);
        }
        System.out.println("执行了环绕通知的myAroud方法,在目标的方法之前输出日志时间"+new Date());
        Object str=null;
        if("小红".equals(name)){
            //执行目标方法(doFirst)
        str=pjp.proceed();//pjp.proceed()相当于Maethod.invoke() 表示执行doFirst中的方法
        }
        System.out.println("环绕通知之后,在你的目标方法之后,新增事务提交");
        return str;  //该处返回啥,就是啥。如果是str就是doFirst的返回参数,如果是”abc“,返回的就是abc
    }
}

配置文件:

<!--        声明目标对象-->
    <bean id="someservice" class="com.liuhaiyang.service.Impl.SomeServiceImpl"/>

<!--    声明切面类对象-->
    <bean id="myaspect" class="com.liuhaiyang.handle.MyAspect"/>
<!--  声明自动代理生成器:-->
    <aop:aspectj-autoproxy />

测试类:

package com.liuhaiyang;

import com.liuhaiyang.service.Impl.SomeServiceImpl;
import com.liuhaiyang.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class test01 {

    @Test
    public void test02(){
        String conf="application.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(conf);
        SomeService someservice=(SomeService) ctx.getBean("someservice");
//        String res=someservice.doOther("瓦达",30);
//        System.out.println(res);
//        someservice.doSome("周爽",25);
        String name=someservice.doFirst("小红");
        System.out.println(name);
    }
}

结果截图:

 2.4 @AfterThrowing:异常通知

 /*
    异常通知方法的定义
    1.方法是public  2.方法没有返回值,是void   3.方法名称自定义
    4.方法有参数是Exception;
    */
    /**
     * @AfterThrowing:异常通知
     *  属性:value:切入点表达式
     *      throwing 自定义变量,表示目标方法抛出的异常。 变量名必须和通知方法的形参名一样
     *  位置:在方法的上面
     *  特点:1.在目标方法抛出异常后执行的,没有异常不执行
     *      2.能获取到目标方法的异常信息
     *      3.不是异常处理程序。可以的得到发生异常的通知,可以发送邮件,短信通知开发人员。
     *      可以看作目标方法的监控程序。
     *      相当于:
     *      try{
     *         execution(* *..SomeServiceImpl.doSecond(..))"
     *      } catch(Exception ex){
     *          System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因:"+ex.getMessage());
     *      }
     */

service接口:

package com.liuhaiyang.service;

public interface SomeService {

    void doSecond(String name);

}

实现类:

package com.liuhaiyang.service.Impl;

import com.liuhaiyang.service.SomeService;


public class SomeServiceImpl implements SomeService {

    @Override
    public void doSecond(String name) {
        int a=10/0;
        System.out.println(name+"执行了业务方法doSecond");
    }

}

异常的增强功能:

package com.liuhaiyang.handle;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect  //该注解表示这个类是切面类
public class MyAspect {
   
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
    public void myAfterThrowing(Exception ex){
        System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因:"+ex.getMessage());
        /*
        异常发生可以做的一些事件:  记录异常发生时间,位置等信息。  发送邮件,短信通知开发人员
         */
    }
}

配置文件同上。没啥好写的,以及写好几遍了

测试类:

 @Test
    public void test02(){
        String conf="application.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(conf);
        SomeService someservice=(SomeService) ctx.getBean("someservice");
        someservice.doSecond("小路");
    }

结果截图:

2.5 @After:最终通知

/**
     * @After:最终通知
     * 属性:value切入点表达式
     * 位置:在方法的上面
     * 特点:1.在目标方法之后执行的。
     *      2.总是会被执行。
     *      3.可以用来做程序最后的收尾工作。例如清除临时数据,变量,清除内存
     *      相当于:
     *      try(){
     *          SomeServiceImpl.doThird(..)
     *      }finally(){
     *          myAfter();
     *      }
     */

service接口:

package com.liuhaiyang.service;

public interface SomeService {

    void doThird(String naem);

}

实现类:

package com.liuhaiyang.service.Impl;

import com.liuhaiyang.service.SomeService;


public class SomeServiceImpl implements SomeService {

    @Override
    public void doThird(String name) {
        System.out.println(name+"执行了业务方法doThird");
    }
}

最终通知增强功能:

package com.liuhaiyang.handle;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect  //该注解表示这个类是切面类
public class MyAspect {
    @After("execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("最终通知,本代码总是会被执行的!!");  //即使是出现异常,该代码也会被执行
    }
}

配置文件同上。

测试类:

@Test
    public void test03(){
        String conf="application.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(conf);
        SomeService someservice=(SomeService) ctx.getBean("someservice");
        someservice.doThird("梨花");
    }

结果截图:

 3. @Pointcut 定义切入点

        当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。

        其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

        给增强功能的代码,其他代码和上面的大同小异。就不写了:

package com.liuhaiyang.handle;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Date;
@Aspect  //该注解表示这个类是切面类
public class MyAspect {
    //前置通知
    @Before("mypt()")
    public void myBefer(){
        System.out.println("前置通知,在目标方法之前执行");
    }
    //最终通知
    @After("mypt()")
    public  void  myArroud() {
        System.out.println("最终通知,在方法的最后执行,无论是否出现异常");
    }
    /**
     * @Pointcut:定义和管理切入点,不是通知注解。  可以写多个
     * 属性:value 切入点表达式(可以省略)
     * 位置: 在一个自定义方法的上面,这个方法可以看做是切入点表达式的别名。
     *        其他的通知注解中,可以使用方法名称,就表示使用这个切入点表达式
     */
    @Pointcut("execution(* *..SomeServiceImpl.doThird(..))")
    public void mypt(){
        //无需代码
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值