写在前面😘
大一电子信息工程新生,请多多关照,希望能在 InfoQ 社区记录自己的学习历程!
【Spring 学习笔记】 系列教程基于 Spring 5.2.10.RELEASE讲解。
一、环境准备
添加 AOP 依赖
在pom.xml文件里添加 Spring AOP 和 AspectJ 的 jar 包依赖
<dependencies> <!--包含Spring AOP:有基本的AOP功能--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <!--AspectJ框架有更强大的AOP功能--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency></dependencies>
复制代码
创建目标接口和实现类
/*UserService接口*/public interface UserService { //自我介绍方法 public string hello(String username,int age);}/*UserServiceImpl实现类*/@Repositorypublic class UserServiceImpl implements UserService { @Override public String hello(String username, int age) { return "username:" + username + ",age:" + age; }}
复制代码
创建通知类
/*通知类*/@Component//将这个类定义成 Bean@Aspect//将这个Bean定义为切面public class MyAdvice { //指定UserService类中的hello方法为切入点 @Pointcut("execution(* com.bighorn.service.UserService.hello(..))") private void pt1(){}}
复制代码
创建 Spring 核心配置类
/*Spring核心配置类*/@Configuration@ComponentScan("com.bighorn") //开启注解扫描@EnableAspectJAutoProxy //开启 AspectJ 的自动代理public class SpringConfig {}
复制代码
编写运行程序
public class App { public static void main(String[] args) throws SQLException { //获取配置类初始化容器 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); //从容器中获取UserService对象 UserService UserService = context.getBean(UserService.class); //调用UserService的方法 String user = UserService.hello("bighorn", 18); System.out.println(user); }}
复制代码
二、JoinPoint 和 ProceedingJoinPoint
通知方法中如果有 JoinPoint 或者 ProceedingJoinPoint 参数,必须要把他们放在第一位!
JoinPoint 接口
在切面方法中添加 JoinPoint 参数,就可以获取到封装了该方法信息的 JoinPoint 对象
适用场景:前置通知、后置通知、异常通知、返回通知
常用 API
目标方法名: joinPoint.getSignature().getName());
目标方法所属类的简单类名:joinPoint.getSignature().getDeclaringType().getSimpleName());
目标方法所属类的类名:joinPoint.getSignature().getDeclaringTypeName();
目标方法声明类型:Modifier.toString(joinPoint.getSignature().getModifiers()));
案例 1
/*前置通知*/@Before("pt1()")public void before(JoinPoint jp) { System.out.println("目标方法名为:" + jp.getSignature().getName()); System.out.println("目标方法所属类的简单类名:" + jp.getSignature().getDeclaringType().getSimpleName()); System.out.println("目标方法所属类的类名:" + jp.getSignature().getDeclaringTypeName()); System.out.println("目标方法声明类型:" + Modifier.toString(jp.getSignature().getModifiers())); //获取传入目标方法的参数 Object[] args = jp.getArgs(); for (int i = 0; i < args.length; i++) { System.out.println("第" + (i+1) + "个参数为:" + args[i]); } // System.out.println("目标对象:" + jp.getTarget()); System.out.println("代理对象:" + jp.getThis());}
复制代码
运行结果如下👇
注意点
仔细观察,发现代理对象自己和被代理对象输出的内存地址一样啊!
其实不然,两个不同的对象内存地址不可能一样,代理类其实内部调用了目标类的方法,上面的目标类和代理类打印调用的是toString(),但是代理类调用的其实是目标类的toString(),所以打印的地址一样。但是你如果用==比较结果就是false了。
我们也可以通过 getClass()方法获取它们的 class 对象并打印出来,比较一下就会发现不同了。
System.out.println("目标对象:" + jp.getTarget().getClass());System.out.println("代理对象:" + jp.getThis().getClass());
复制代码
ProceedingJoinPoint 接口
ProceedingJoinPoint 接口 是 JoinPoint 的子接口,适用场景:只用在环绕通知中。
而且环绕通知方法中也必须有 ProceedingJoinPoint 这个形参,不然就没意义了。
proceed 方法
Object proceed() throws Throwable //执行目标方法Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法
复制代码
案例 2
对原始方法的第一个参数username进行拦截,把英文名变成大写!
/*环绕通知*/@Around("pt1()")public Object around(ProceedingJoinPoint pjp) { Object result = null; try { //获取传入目标方法的参数 Object[] args = pjp.getArgs(); //前置通知 System.out.println("目标方法执行前...原参数:"+Arrays.toString(args)); //将传入的英文名变成大写 args[0] = args[0].toString().toUpperCase(); //用新的参数args执行目标方法 result = pjp.proceed(args); //返回通知 System.out.println("目标方法返回结果后...新参数:"+ Arrays.toString(args)); } catch (Throwable e) { //异常通知 System.out.println("执行目标方法异常后..."); e.printStackTrace(); } //后置通知 System.out.println("目标方法执行后..."); return result;}
复制代码
运行结果如下👇
三、获取返回值
对于目标方法的返回值,只有返回通知@AfterReturing和环绕通知@Around这两个通知类型可以获取,下面来介绍如何获取。
环绕通知获取返回值
返回通知获取返回值
/*返回后通知*/@AfterReturning(value = "pt1()", returning = "ret")public void afterReturning(Object ret) { System.out.println("返回通知,获取原始方法返回值--->" + ret);}
复制代码
运行结果如下👇
四、获取异常
对于目标方法抛出异常,只有异常通知@AfterThrowing和环绕通知@Around这两个通知类型可以获取,下面来介绍如何获取。
环绕通知获取异常
try{ ret = pjp.proceed();}catch(Throwable e){ //根据业务需求处理异常 e.printStackTrace();}
复制代码
异常通常获取异常
//异常后通知@AfterThrowing(value = "pt1()",throwing = "t")public void afterThrowing(Throwable t) { System.out.println("异常通知获取到的异常"+t);}
复制代码
写在后面🍻
感谢观看啦✨
有什么不足,欢迎指出哦💖
评论