有关Spring的所有文章都收录于我的专栏:👉Spring👈
目录
一、前言
二、演示
三、切面类中声明通知方法
四、使用
相关文章
【Spring(一)】如何获取对象(Bean) | 【Spring(一)】如何获取对象(Bean) |
【Spring(二)】java对象属性的配置(Bean的配置) | 【Spring(二)】java对象属性的配置(Bean的配置) |
【Spring(三)】熟练掌握Spring的使用 | 【Spring(三)】熟练掌握Spring的使用 |
【Spring(四)】Spring基于注解的配置方式 | 【Spring(四)】Spring基于注解的配置方式 |
【Spring(五)】引入篇:AOP的底层原理之动态代理 | 【Spring(五)】引入篇:AOP的底层原理之动态代理 |
一、前言
通过我们上一节对AOP的引入和底层原理的讲解,我相信大家对AOP有了很深的理解。即使我们还没讲AOP的使用。大家肯定不希望做一个只会API的程序员。只要对原理有了解,一个技术也就能很快的上手。
二、演示
我们在正式开始之前,先演示一下基本使用。
<context:component-scan base-package="com.jl.spring.aop.exer"/>
<aop:aspectj-autoproxy/>
public interface Cal {
public double cal1(int n);
public double cal2(int n);
}
package com.jl.spring.aop.exer;
import org.springframework.stereotype.Component;
/**
* @author long
* @date 2022/9/11
*/
@Component
public class MyCal implements Cal{
@Override
public double cal1(int n) {
int result=0;
for (int i = 1; i <= n; i++) {
result += i;
}
System.out.println("1+2+...=" + result);
return result;
}
@Override
public double cal2(int n) {
int result = 0;
if (n == 0){
System.out.println("1+2+...=" + result);
return result;
}
if (n == 1){
System.out.println("1+2+...=" + 1);
return 1;
}
result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
System.out.println("1+2+...=" + result);
return result;
}
}
package com.jl.spring.aop.exer;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author long
* @date 2022/9/11
*/
@Aspect
@Component
public class MyCalAspect {
@Before(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))")
public void showBegin(){
System.out.println("开始执行计算");
}
@AfterReturning(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))")
public void showEndn(){
System.out.println("结束执行计算");
}
}
测试类
@Test
public void MyCalAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("mycal.xml");
Cal bean = ioc.getBean(Cal.class);
bean.cal1(2);
}
运行结果:
我们主要介绍基于注解的AOP的使用
三、切面类中声明通知方法
- 前置通知:@before
- 返回通知:@AfterReturning
- 异常通知:@AfterThrowing
- 后置通知:@After
- 环绕通知:@Around
这五个注解,我们在【使用】中会全部进行讲解。
四、使用
和前面几节中使用注解一样,我们想要让Spring容器将我们的类扫描实例化到容器中,我们就得在xml配置文件中告诉Spring容器要扫描的包,同时还要开始基于注解的AOP功能。
<!--需要扫描的包-->
<context:component-scan base-package="com.jl.spring.aop.exer"/>
<!--开启基于注解的AOP功能-->
<aop:aspectj-autoproxy/>
AOP切入表达式
exexution([权限修饰符][返回值类型][简单类名/全类名][方法名][参数类名])
举例:
// public修饰符 double返回值类型 com.ji.spring.aop.exer.MyCal全类名 *所有的方法 (..)匹配任意数量、任意类型的参数
execution(public double com.jl.spring.aop.exer.MyCal.*(..))
- 切入表达式也可以指向类的方法, 这时切入表达式会对该类/对象生效。
- 切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效。
- 切入表达式也可以对没有实现接口的类,进行切入。
JoinPoint
JoinPoint在调用前置通知获取到调用方法的签名, 和其它相关信息。
package com.jl.spring.aop.exer;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author long
* @date 2022/9/11
*/
@Aspect
@Component
public class MyCalAspect {
@Before(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))")
public void showBegin(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println(signature.getName() +"开始执行计算");
}
// 其他通知也一样
}
运行结果:
joinPoint常用的方法
public void beforeMethod(JoinPoint joinPoint){
joinPoint.getSignature().getName(); // 获取目标方法名
joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属
类的简单类名
joinPoint.getSignature().getDeclaringTypeName(); // 获取目标方法所属类的类名
joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
joinPoint.getTarget(); // 获取被代理的对象
joinPoint.getThis(); // 获取代理对象自己
}
Aop返回通知获取结果
package com.jl.spring.aop.exer;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author long
* @date 2022/9/11
*/
@Aspect
@Component
public class MyCalAspect {
@AfterReturning(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))",returning = "res")
public void showEndn(Object res){
System.out.println("结束执行计算"+"结果是=" + res);
}
}
- 在@AfterReturning直接的参数中使用returning来接收返回的结果。通过方法的参数来传递,对此结果可以进行相应的后续处理。
- returning底层是通过调用MyCal类的cal1()来获取到结果的。
结果截图:
AOP异常通知中获取异常
我们这里制造一个异常,看一下我们的异常通知。
package com.jl.spring.aop.exer;
import org.springframework.stereotype.Component;
/**
* @author long
* @date 2022/9/11
*/
@Component
public class MyCal implements Cal{
@Override
public double cal1(int n) {
int result=0;
for (int i = 1; i <= n; i++) {
result = result / 0;
}
System.out.println("1+2+...=" + result);
return result;
}
}
package com.jl.spring.aop.exer;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author long
* @date 2022/9/11
*/
@Aspect
@Component
public class MyCalAspect {
@AfterThrowing(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))",throwing = "throwable")
public void showExceptionLog(Throwable throwable) {
System.out.println("异常通知"+throwable);
}
}
结果截图
AOP环绕通知(了解)
环绕通知可以完成其他四个通知可以做的事情。
package com.jl.spring.aop.exer;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Aspect //表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定...)]
@Component //会注入SmartAnimalAspect2到容器
public class MyCalAspect2 {
//演示环绕通知的使用-了解
//1. @Around: 表示这是一个环绕通知[完成其它四个通知的功能]
//2. value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))" 切入点表达式
//3. doAround 表示要切入的方法 - 调用结构 try-catch-finally
@Around(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object result = null;
String methodName = joinPoint.getSignature().getName();
try {
//1.相当于前置通知完成的事情
Object[] args = joinPoint.getArgs();
List<Object> argList = Arrays.asList(args);
System.out.println("AOP环绕通知[-前置通知]" + methodName + "方法开始了--参数有:" + argList);
//在环绕通知中一定要调用joinPoint.proceed()来执行目标方法
result = joinPoint.proceed();
//2.相当于返回通知完成的事情
System.out.println("AOP环绕通知[-返回通知]" + methodName + "方法结束了--结果是:" + result);
} catch (Throwable throwable) {
//3.相当于异常通知完成的事情
System.out.println("AOP环绕通知[-异常通知]" + methodName + "方法抛异常了--异常对象:" + throwable);
} finally {
//4.相当于最终通知完成的事情
System.out.println("AOP环绕通知[-后置通知]" + methodName + "方法最终结束了...");
}
return result;
}
}
结果截图:
AOP切入点表达式重用
package com.jl.spring.aop.exer;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author long
* @date 2022/9/11
*/
@Aspect
@Component
public class MyCalAspect {
@Pointcut(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))")
public void myCut(){
System.out.println("123");
}
@Before(value = "myCut()")
public void showBegin(){
System.out.println("开始执行计算");
}
@AfterReturning(value = "myCut()")
public void showEnd(){
System.out.println("结束执行计算");
}
@AfterThrowing(value = "myCut()")
public void showExceptionLog() {
System.out.println("异常通知");
}
@After(value = "myCut()")
public void showFinallyEndLog() {
System.out.println("最终通知");
}
}
结果截图:
通过切入点表达式的重用可以统一管理切入点。
切面的优先级问题
我们可以通过Order(value=值)
来控制切面的优先级,值越小,优先级越高。
package com.jl.spring.aop.exer;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author long
* @date 2022/9/11
*/
@Aspect
@Order(value = 2)
@Component
public class MyCalAspect {
@Before(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))")
public void showBegin(){
System.out.println("开始执行计算");
}
@AfterReturning(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))")
public void showEndn(){
System.out.println("结束执行计算");
}
@AfterThrowing(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))")
public void showExceptionLog() {
System.out.println("异常通知");
}
@After(value = "execution(public double com.jl.spring.aop.exer.MyCal.*(..))")
public void showFinallyEndLog() {
System.out.println("最终通知");
}
}
当我们有多个切面类的时候,他的各个切面通知的顺序相当于javaWeb部分过滤器Filter过滤链
的那个顺序。
基于XML配置AOP
注意:在配置之前,需要将之间用注解配的切面类注销
<!--xml方式-->
<bean class="com.jl.spring.aop.exer.MyCalAspect" id="myCalAspect"/>
<bean class="com.jl.spring.aop.exer.MyCal" id="myCal"/>
<aop:config>
<aop:pointcut id="myCut" expression="execution(public double com.jl.spring.aop.exer.MyCal.*(..))"/>
<aop:aspect ref="myCalAspect">
<aop:before method="showBegin" pointcut-ref="myCut"/>
<aop:after-returning method="showEndn" pointcut-ref="myCut"/>
</aop:aspect>
</aop:config>
package com.jl.spring.aop.exer;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author long
* @date 2022/9/11
*/
public class MyCalAspect {
public void showBegin(){
System.out.println("开始执行计算");
}
public void showEndn(){
System.out.println("结束执行计算");
}
}
测试类
package com.jl.spring.aop.exer;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author long
* @date 2022/9/11
*/
public class MyCalTest {
@Test
public void MyCalAnnotation(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("mycal.xml");
Cal bean = ioc.getBean(Cal.class);
bean.cal1(2);
}
}
如果文章中有描述不准确或者错误的地方,还望指正。您可以留言📫或者私信我。🙏
最后希望大家多多 关注+点赞+收藏^_^,你们的鼓励是我不断前进的动力!!!
感谢感谢~~~🙏🙏🙏