其实在做AOP切面编程时,最常使用也必备的一个标签就是,< aop:aspectj-autoproxy></aop:aspectj-autoproxy>,
我们在容器中需要添加这个元素,当Spring IOC容器侦测到bean配置文件中的< aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的bean创建代理。
同时在现在的spring中使用AOP切面有两种方式,分别是AspectJ注解或基于XML配置的AOP,
下面我依次和大家介绍一下这两种方式的使用。
======================================================================================
在上一篇文章中我也和大家将了关于spring中注解开发的强大,所以关于AOP开发我们同样也可以使用注解的形式来进行编写,下面我来和大家介绍一下如何使用注解方式书写AOP。
首先要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例。
当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创建代理。
在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要包含很多通知。通知是标注有某种注解的简单的Java方法。
AspectJ支持5种类型的通知注解:
- @Before:前置通知,在方法执行之前执行
- @After:后置通知,在方法执行之后执行
- @AfterRunning:返回通知,在方法返回结果之后执行
- @AfterThrowing:异常通知,在方法抛出异常之后执行
- @Around:环绕通知,围绕着方法执行
这五种通知注解后面还可以跟特定的参数,来指定哪一个切面方法在哪一个方法执行时触发。那么具体操作是怎么样的呢?
这里就需要和大家介绍一个名词:“切入点表达式”,通过在注解中加入该表达式参数,我们就可以通过表达式的方式定位一个或多个具体的连接点,
切入点表达式的语法格式规范是:
execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名] ([参数列表]))
其中在表达式中有两个常用的特殊符号:
星号“ * ”代表所有的意思,星号还可以表示任意的数值类型
“.”号:“…”表示任意类型,或任意路径下的文件,
在这里举出几个例子:
表达式:
execution(* com.atguigu.spring.ArithmeticCalculator.*(…))
含义:
ArithmeticCalculator接口中声明的所有方法。第一个“星号”代表任意修饰符及任意返回值。第二个“星号”代表任意方法。“…”匹配任意数量、任意类型的参数。若目标类、接口与该切面类在同一个包中可以省略包名。
表达式:
execution(public * ArithmeticCalculator.*(…))
含义:
ArithmeticCalculator接口的所有公有方法
表达式:
execution(public double ArithmeticCalculator.*(…))
含义:
ArithmeticCalculator接口中返回double类型数值的方法
表达式:
execution(public double ArithmeticCalculator.*(double, …))
含义:
第一个参数为double类型的方法。“…” 匹配任意数量、任意类型的参数。
表达式:
execution(public double ArithmeticCalculator.*(double, double))
含义:
参数类型为double,double类型的方法
这里还有一个定位最模糊的表达式:
execution("* *(…)")
表示任意包下任意类的任意方法,但是这个表达式千万别写,哈哈,不然你每一个执行的方法都会有通知方法执行的!
同时,在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。
如:
execution (* .add(int,…)) || execution( *.sub(int,…))
表示任意类中第一个参数为int类型的add方法或sub方法
现在我们已经知道了注解和切入点表达式的使用,那么接下来就是进行实践了,
对于切入点表达式,我们可以直接在注解中使用“”写在其中,还可以在@AfterReturning注解和@AfterThrowing注解中将切入点赋值给pointcut属性,但是在其他的注解中没有pointcut这个参数。
将切入点表达式应用到实际的切面类中如下:
@Aspect //切面注解
@Component //其他业务层
public class LogUtli {
// 方法执行开始,表示目标方法是com.spring.inpl包下的任意类的任意以两个int为参数,返回int类型参数的方法
@Before("execution(public int com.spring.inpl.*.*(int, int))")
public static void LogStart(JoinPoint joinPoint) {
System.out.println("通知记录开始...");
}
// 方法正常执行完之后
/**
* 在程序正常执行完之后如果有返回值,我们可以对这个返回值进行接收
* returning用来接收方法的返回值
* */
@AfterReturning(pointcut="public int com.spring.inpl.*.*(int, int)",returning="result")
public static void LogReturn(JoinPoint joinPoint,Object result) {
System.out.println("【" + joinPoint.getSignature().getName() + "】程序方法执行完毕了...结果是:" + result);
}
}
以上只是一个最简单的通知方法,但是在实际的使用过程中我们可能会将多个通知方法切入到同一个目标方法上去,比如同一个目标方法上既有前置通知、又有异常通知和后置通知。
但是这样我们也只是在目标方法执行时切入了一些通知方法,那么我们能不能在通知方法中获取到执行的目标方法的一些信息呢?当然是可以的。
在这里我们就可以使用JoinPoint接口来获取到目标方法的信息,如方法的返回值、方法名、参数类型等。
如我们在方法执行开始前,获取到该目标方法的方法名和输入的参数并输出。
// 方法执行开始
@Before("execution(public int com.spring.inpl.*.*(int, int))")
public static void LogStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs(); //获取到参数信息
Signature signature = joinPoint.getSignature(); //获取到方法签名
String name = signature.getName(); //获取到方法名
System.out.println("【" + name + "】记录开始...执行参数:" + Arrays.asList(args));
}
对于有些目标方法在执行完之后可能会有返回值,或者方法中途异常抛出,那么对于这些情况,我们应该如何获取到这些信息呢?
首先我们来获取当方法执行完之后获取返回值,
在这里我们可以使用@AfterReturning注解,该注解表示的通知方法是在目标方法正常执行完之后执行的。
在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。
该属性的值即为用来传入返回值的参数名称,但是注意必须在通知方法的签名中添加一个同名参数。
在运行时Spring AOP会通过这个参数传递返回值,由于我们可能不知道返回值的类型,所以一般将返回值的类型设置为Object型。
与此同时,原始的切点表达式需要出现在pointcut属性中,
如下所示:
// 方法正常执行完之后
/**
* 在程序正常执行完之后如果有返回值,我们可以对这个返回值进行接收
* returning用来接收方法的返回值
* */
@AfterReturning(pointcut="public int com.spring.inpl.*.*(int, int)",returning="result")
public static void LogReturn(JoinPoint joinPoint,Object result) {
System.out.println("【" + joinPoint.getSignature().getName() + "】程序方法执行完毕了...结果是:" + result);
}
对于接收异常信息,方法其实是一样的。
我们需要将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。
如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行。
实例如下:
// 异常抛出时
/**
* 在执行方法想要抛出异常的时候,可以使用throwing在注解中进行接收,
* 其中value指明执行的全方法名
* throwing指明返回的错误信息
* */
@AfterThrowing(pointcut="public int com.spring.inpl.*.*(int, int)",throwing="e")
public static void LogThowing(JoinPoint joinPoint,Object e) {
System.out.println("【" + joinPoint.getSignature().getName() +"】发现异常信息...,异常信息是:" + e);
}
我们在上面介绍通知注解的时候,大家应该也看到了其实还有一个很重要的通知——环绕通知,
环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。这就意味着我们需要在方法中传入参数ProceedingJoinPoint来接收方法的各种信息。
注意:
环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。
具体使用可以看下面这个实例:
/**
* 环绕通知方法
* 使用注解@Around()
* 需要在方法中传入参数proceedingJoinPoint 来接收方法的各种信息
* 使用环绕通知时需要使用proceed方法来执行方法
* 同时需要将值进行返回,环绕方法会将需要执行的方法进行放行
* *********************************************
* @throws Throwable
* */
@Around("public int com.spring.inpl.*.*(int, int)")
public Object MyAround(ProceedingJoinPoint pjp) throws Throwable {
// 获取到目标方法内部的参数
Object[] args = pjp.getArgs();
System.out.println("【方法执行前】");
// 获取到目标方法的签名
Signature signature = pjp.getSignature();
String name = signature.getName();
Object proceed = null;
try {
// 进行方法的执行
proceed = pjp.proceed();
System.out.println("方法返回时");
} catch (Exception e) {
System.out.println("方法异常时" + e);
}finally{
System.out.println("后置方法");
}
//将方法执行的返回值返回
return proceed;
}
那么现在这五种通知注解的使用方法都已经介绍完了,
我们来总结一下这几个通知注解都在同一个目标方法中时的一个执行顺序。
在正常情况下执行:
@Before(前置通知)—>@After(后置通知)---->@AfterReturning(返回通知)
在异常情况下执行:
@Before(前置通知)—>@After(后置通知)---->@AfterThrowing(异常通知)
当普通通知和环绕通知同时执行时:
执行顺序是:
环绕前置----普通前置----环绕返回/异常----环绕后置----普通后置----普通返回/异常
对于上面的通知注解,我们都是在每一个通知注解上都定义了一遍切入点表达式,
但是试想一个问题,如果我们不想给这个方法设置通知方法了,或者我们想要将这些通知方法切入到另一个目标方法,那么我们岂不是要一个一个的更改注解中的切入点表达式吗?这样也太麻烦了吧?
所以spring就想到了一个办法,重用切入点表达式。
也就是说将这些会重复使用的切入点表达式用一个方法来表示,那么我们的通知注解只需要调用这个使用了该切入点表达式的方法即可实现和之前一样的效果,这样的话,我们即使想要更改切入点表达式的接入方法,也不用一个一个的去通知注解上修改了。
获取可重用的切入点表达式的方法是:
-
随便定义一个void的无实现的方法
-
为方法添加注解@Pointcut()
-
在注解中加入抽取出来的可重用的切入点表达式
-
使用value属性将方法加入到对应的切面函数的注解中
完整实例如下:
@Aspect //切面注解
@Component //其他业务层
public class LogUtli {
/**
* 定义切入点表达式的可重用方法
* */
@Pointcut("execution(public int com.spring.inpl.MyMathCalculator.*(int, int))")
public void MyCanChongYong() {}
// 方法执行开始
@Before("MyCanChongYong()")
public static void LogStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs(); //获取到参数信息
Signature signature = joinPoint.getSignature(); //获取到方法签名
String name = signature.getName(); //获取到方法名
System.out.println("【" + name + "】记录开始...执行参数:" + Arrays.asList(args));
}
// 方法正常执行完之后
/**
* 在程序正常执行完之后如果有返回值,我们可以对这个返回值进行接收
* returning用来接收方法的返回值
* */
@AfterReturning(value="MyCanChongYong()",returning="result")
public static void LogReturn(JoinPoint joinPoint,Object result) {
System.out.println("【" + joinPoint.getSignature().getName() + "】程序方法执行完毕了...结果是:" + result);
}
// 异常抛出时
/**
* 在执行方法想要抛出异常的时候,可以使用throwing在注解中进行接收,
* 其中value指明执行的全方法名
* throwing指明返回的错误信息
# 总结
总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。
如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。
这些视频如果需要的话,可以无偿分享给大家,**[点击这里即可免费领取](https://gitee.com/vip204888/java-p7)**
* returning用来接收方法的返回值
* */
@AfterReturning(value="MyCanChongYong()",returning="result")
public static void LogReturn(JoinPoint joinPoint,Object result) {
System.out.println("【" + joinPoint.getSignature().getName() + "】程序方法执行完毕了...结果是:" + result);
}
// 异常抛出时
/**
* 在执行方法想要抛出异常的时候,可以使用throwing在注解中进行接收,
* 其中value指明执行的全方法名
* throwing指明返回的错误信息
# 总结
总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。
如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。
这些视频如果需要的话,可以无偿分享给大家,**[点击这里即可免费领取](https://gitee.com/vip204888/java-p7)**
![](https://img-blog.csdnimg.cn/img_convert/d846d36e1d516226d8bc50fcedf2fb2d.png)