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 {@Overridepublic void add() {System.out.println("student add...");}@Overridepublic void delete() {System.out.println("student delete...");}@Overridepublic void update() {System.out.println("student update...");}@Overridepublic 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);获取原始函数执行的返回值
获取异常数据(了解)
例