一、AOP简介
注意:跑update、delete、select方法的时候,也想能够运行到该万次耗时计算的功能的前提是: update、delete、select方法中没有该功能 并且不动update、delete、select方法中的代码逻辑
再次理解:
连接点:也可以理解为所有的方法
切入点:代表追加某个功能的方法
通知:也就是连接点共性的功能
切面:可以说是绑定通知和切入点的关系
通知类:MyAdvice
二、AOP入门
2.1、没有使用AOP思想时代码演示如下:
App:
SpringConfig:
com.itheima包下加了bean注解的BookDaoImpl:
App测试程序中调用bean中的save()方法时输出结果如下所示:
App测试程序中调用bean中的update()方法时输出结果如下所示:
思考:假设update()方法当中也想打印系统时间的功能,但前提是在update()方法中不能写该打印系统时间的功能(也就是说:不动update方法中的逻辑代码)的情况下,有什么办法能够做到呢:
2.2、那么就用到了AOP面向切面编程的思想
第一步:导入坐标 <aspectjweaver>
第二步:制作连接点方法 (也就是上面演示的save()、update()方法)
第三步:制作共性功能 (也就是说把想要共性的打印系统时间的功能抽出来单独放入通知类MyAdvice中)
第四步:定义切入点(也就是说,哪个类中的方法想添加新的共性功能,那么就切入到该类的方法名下)
第五步:绑定切入点与通知关系(切面)
package com.itheima.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component // 通知类必须配置成Spring管理的bean
@Aspect // 该注解的目的是让spring知道这个MyAdvice类是专门储存处理共性功能的类
public class MyAdvice {
/**
*
* @Pointcut("execution(void com.itheima.dao.BookDao.update())") 切入的是该包下的update方法中
* 第二步:、设置切入点 (方法名任意取 这里取的是pt)
*/
// execution(void com.itheima.dao.BookDao.update()) 表示切入的是该包下的update()方法(返回类型void)
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
/**
* @Before("pt()") 注解
* 绑定切入点与通知之间的关系 : 设置在切入点pt()的前面运行当前操作(前置通知)
*/
@Before("pt()")
/**
* 第一步:、把共性功能【打印系统时间的功能】抽出写入单独的方法中(方法名任意取 这里取的是method)
*/
public void method(){
System.out.println(System.currentTimeMillis());
}
}
总体代码如下所示:
App:
package com.itheima;
import com.itheima.config.SpringConfig;
import com.itheima.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args) {
// 1、获取IOC容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
// 2、获取IOC容器中管理的bean
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.update();
}
}
SpringConfig: (注解不能少)
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy // 该注解的作用是:告诉spring项目中有用注解开发的AOP
public class SpringConfig {
}
com.itheima包下含有bean注解的BookDaoImpl:
package com.itheima.dao.impl;
import com.itheima.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println(System.currentTimeMillis()); // 打印系统时间的功能
System.out.println("book dao save ...");
}
public void update(){
System.out.println("book dao update ...");
}
}
MyAdvice:
package com.itheima.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component // 通知类必须配置成Spring管理的bean
@Aspect // 该注解的目的是让spring知道这个MyAdvice类是专门储存处理共性功能的类
public class MyAdvice {
/**
*
* @Pointcut("execution(void com.itheima.dao.BookDao.update())") 切入的是该包下的update方法中
* 第二步:、设置切入点 (方法名任意取 这里取的是pt)
*/
// execution(void com.itheima.dao.BookDao.update()) 表示切入的是该包下的update()方法(返回类型void)
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
/**
* @Before("pt()") 注解
* 第三步:绑定切入点与通知之间的关系 : 设置在切入点pt()的前面运行当前操作(前置通知)
*/
@Before("pt()")
/**
* 第一步:、把共性功能【打印系统时间的功能】抽出写入单独的方法中(方法名任意取 这里取的是method)
*/
public void method(){
System.out.println(System.currentTimeMillis());
}
}
运行程序结果如下所示:
会发现拿到的IOC容器管理的bean后,调用的update()方法中本来没有打印系统时间的功能,但是结果我们缺拿到了该功能,这就是面向切面编程AOP的作用
三、AOP内部是使用代理对象形式进行工作的
四、AOP切入点表达式
那么我们再描述切入点的时候,要按照上面的切入点表达式标准格式写,但是当我们项目中有很多需要描述切入点方法的时候,我们要按着标准的格式写的话是非常多的麻烦的,因此我们有什么简单的方法来简化上面的标准格式呢:
五、AOP通知类型
5.1、前置通知:@Before("#")注解
代码演示如下:
BookDaoImpl:
通知类:
程序测试:
输出结果分析:(使用前置通知注解后,输出结果的时候共性功能会先被调用输出)
5.2、后置通知:@After("#")注解
代码演示如下:
通知类:
输出结果:
5.3、环绕通知:@Aound(“#”)注解 (以后常用)
代码演示如下:
BookDaoImpl:
通知类:(假定BookDaoImpl类中的update方法在不动代码的情况下,也想执行共性方法around方法)
代码测试如下:
会发现:使用环绕通知注解后,相比着前置/后置通知,输出结果不一样了,前置/后置通知不仅会调用原始自己的update方法,也会调用共性的方法,而使用环绕通知注解后,调用update()方法的时候,缺只调用了共性的功能 around()方法,update()方法缺没有调用
那么我们怎么让代码即调用输出原始自己的update()方法,又调用输出共性功能的方法呢:
1、共性功能方法中加参数ProceedingJoinPoint
2、pjp.proceed() 【该意思就是调用自己原始的方法如update()方法,注意:有返回类型的记得接收返回值类型】
注意1:pjp.proceed()放在共性功能输出around before advice 和 around after advice 的前后中间位置都可以,放在前面的话就先调用输出原始的update()方法.... pjp.proceed()其实就是调用执行原始的update()方法的
注意2:pjp.proceed()方法:其实就是调用原始自己的方法的(如原始自己的update方法)
原始自己的方法中如果有返回值类型的话,还可以接收返回值类型和返回值的值,如果不接收,那么就会报错
输出结果演示:
5.3.1、但是使用环绕通知会有一个问题
就是:当我们的原始连接点的方法如:select()方法的有返回值类型的时候【如:int select();】,那么就有可能不处理的话就会抛出异常
代码演示如下:
BookDaoImpl:
原始select方法中有返回值类型int
通知类:
程序测试结果:
5.3.2、出现异常的原因(pjp.proceed();到底调哪个方法,就看测试程序调用的是哪个方法,这里程序测试时调用的时select()方法,因此pjp.proceed()调用的就是select()方法)
因此我们上面的代码,需要pjp.proceed()在调用原始的方法时,当原始的方法有返回类型的时候,需要接收一下返回类型然后返回,(直接用Object即可,因为Object是int long.....的所有的父亲)