写在前面😘
大一电子信息工程新生,请多多关照,希望能在 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实现类*/
@Repository
public 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);
}
复制代码
写在后面🍻
感谢观看啦✨
有什么不足,欢迎指出哦💖
评论