AOP核心概念
AOP的思想是为了实现 在不惊动我们原始代码的情况下为其增加功能。
在本案例中,增删改查方法中的逻辑是我们书写的原始代码,我们想为其增加记录执行时间的功能,根据上面的概念:
1.增删改查方法中的业务逻辑是"连接点"(我们的原始代码)
2.我们将记录执行时间的代码加以封装为method方法,它是"通知"(需要复用,或者说我们追加的代码)
3.这里定义"通知"的类是MyAdvice,它是"通知类"(里面有通知的类)
4.我们需要一个"切入点"来匹配一个或者多个"连接点",比如这里我们只想记录update和delete方法的执行时间,就用"切入点"来匹配他们两个,而另外两个方法没有被匹配到
5.使用"切面"来匹配切入点和通知之间的关系
入门案例
1.导入AOP相关坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring AOP要用的包 SpringAOP在springContext里面-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
2.定义连接点(Service中我们的原始代码),通知类和通知(我们追加的可复用的代码)
StudentServiceImpl实现StudentService接口
package com.example.test.service.impl;
import com.example.test.dao.StudentDao;
import com.example.test.service.StudentService;
import lombok.Data;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StudentServiceImpl implements StudentService {
@Override
public void add() {
System.out.println("student add...");
}
@Override
public void delete() {
System.out.println("student delete...");
}
@Override
public void update() {
System.out.println("student update...");
}
@Override
public void select() {
System.out.println("student select...");
}
}
通知类和通知(在里面定义切入点,绑定切入点和通知)
package com.example.test.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
@Aspect //让context知道我们这里要用aop
public class StudentAdvice {
//切入点
@Pointcut("execution(* com.example.test.service.StudentService.*(..))")
public void pt(){}
@After("pt()")
public void getTime(){
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM--dd HH-mm-ss");//打印时间
System.out.println(sdf.format(date));
}
}
这里的getTime通知匹配: com.example.test.service.StudentService中的返回值和参数为任意类型的方法,在连接点后面执行。 (看不懂没关系,后面会讲)
它的作用是返回业务完成后的系统时间。
3.SpringConfig配置类中开启aop
package com.example.test.config;
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan("com.example.test")//包扫描
@PropertySource("db.properties")//加载properties文件
@Import({JdbcConfig.class,MybatisConfig.class})//加载第三方bean
@EnableAspectJAutoProxy //开启AOP
public class SpringConfig {
}
测试代码及结果
可以看到,在add和delete方法被调用后都执行了getTime方法。
工作流程
由上述描述:aop为我们创建的是一个代理对象。
切入点表达式
标准格式
一般情况下: execution (返回值类型 包名.类/接口名.方法名(参数类型))
有接口和其实现类的情况下写它的接口,比较规范
使用通配符描述切入点
* 单个任意(必须有一个)
.. 多个连续任意(也可以没有)
例: execution(* com.test..*value(*))
匹配 com.test包里面的任意 方法名以value结尾的 只有一个传入参数 返回值为任意 的方法
书写技巧
AOP通知类型
@Before
@After
@Around (重点)
定义一个ProceedingJoinPoint对象pjp,调用pjp.proceed()表示在这个位置执行连接点的代码。
注意事项
对于第三点和第四点的解释:你的通知方法可能作用于多个原始方法,他们的返回值类型可能不同。 如果通知方法接收了返回值,那么通知方法return的结果会直接作为返回值赋给原始方法。
测试案例
@Component
@Aspect //让context知道我们这里要用aop
public class StudentAdvice {
//切入点
// @Pointcut("execution(* com.example.test.service.StudentService.select(..))")
// @Pointcut("execution(* com.example.test..*Se*vice.*dd(..))") 只有add方法匹配上了
@Pointcut("execution(int com.example.test.service.StudentService.select(int))")//只匹配select方法
public void pt(){}
@Around("pt()")
public void getTime(ProceedingJoinPoint pjp) throws Throwable {
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM--dd HH-mm-ss");//打印时间
System.out.println("前:"+sdf.format(date));
pjp.proceed();//连接点方法执行
System.out.println("后:"+sdf.format(date));
}
}
测试代码及结果
@AfterReturning(了解)
@AfterThrowing(了解)
AOP通知获取数据
获取参数数据
除了@Around用ProceedingJoinPoint,其他4种都用JoinPoint来获取参数数据
测试案例
例1
修改select方法 (通知方法也要返回int)
测试代码及结果
取到id=1
例2
@Around(value = "pt()")
public int getTime(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();//直接pjp.getArgs()获取参数
System.out.println("修改前的id:"+args[0]);
args[0]=666;//在这里修改传入参数
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM--dd HH-mm-ss");//打印时间
System.out.println("前:"+sdf.format(date));
Object result = pjp.proceed(args);//连接点方法执行,这里传入我们修改后的参数
System.out.println("修改后的id:"+args[0]);
System.out.println("后:"+sdf.format(date));
return (int)result;
}
测试代码同上,结果如下,成功获取修改后的参数为666
获取返回值数据(只有@AfterReturning和@Around能用)
@After作用于原始代码执行完毕后, return前
测试案例
例1
这个通知是在原始方法return后调用的
例2
@Around(value = "pt()")
public int getTime(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();//直接pjp.getArgs()获取参数
Object result = pjp.proceed(args);//连接点方法执行
System.out.println("原来的返回值:"+result);
return 888;
}
总结:
@Around中:
Object[] args = pjp.getArgs();获取传入的参数
Object result = pjp.proceed(args);获取原始函数执行的返回值
获取异常数据(了解)
例