简介
原本的功能都称为连接点
需要追加功能的方法称为切入点
需要追加的功能称为通知
对应包裹着的类称为通知类
一个通知
对应的切入点
称为切面
案例
在dao层有2个方法
配置类也很简单
现在我使用save方法会打印出时间
而使用update不会打印出时间
我们的任务是,当我们执行update时,需要有时间输出,而且还不能改动原本业务
1导包
由于spring-aop是在spring-context里面的默认导入,所以我们只要导入aspect即可
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2抽取共性功能
创建aop包与class
在里面定义我们的共性功能
public void method(){
System.out.println(System.currentTimeMillis());
}
然后增加功能
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类,告诉spring扫描到我之后把我当aop处理
@Aspect
public class MyAdvice {
//设置切入点,要求配置在方法上方 括号内为此方法的返回值,路径与传入参数
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
//建造一个空壳
private void pt(){}
//设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
在SpringConfig核心配置类里告诉spring启动aop编程
@EnableAspectJAutoProxy
主函数类里面启动一下切入点方法即可发现成功执行aop
总结
AOP工作流程
AOP切入点表达式
execution
这个词基本不会变化
public
一般都省略,默认了
User
返回值,需要返回的值自己写
但是如果一个程序中开发了几百个上千个功能,那么肯定要写死了
所以我们希望写一个,能代表一堆,因此,通配符就出来了
这里需要注意的是,当最后的方法里面写了*
时,代表着里面必须有形参,而不是可以没有!
以后的aop都是这么写的:
①我要给所有业务层的find方法加上aop:@Pointcut("execution(* it.calendo.*.*Service.find*(..))")
通知类型
案例:
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt() {
}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2() {
}
//@Before:前置通知,在原始方法运行之前执行
// @Before("pt()")
public void before() {
System.out.println("before advice ...");
}
//@After:后置通知,在原始方法运行之后执行
// @After("pt2()")
public void after() {
System.out.println("after advice ...");
}
//@Around:环绕通知,在原始方法运行的前后执行
@Around("pt2()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用(强制抛异常)
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
// @Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Integer ret = (Integer) pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
//@AfterReturning:返回后通知,在原始方法执行完毕后运行,且原始方法执行过程中未出现异常现象
// @AfterReturning("pt2()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
//@AfterThrowing:抛出异常后通知,在原始方法执行过程中出现异常后运行
// @AfterThrowing("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
}
proceedingJoinPoint
如果没有执行对原始方法的调用,就会产生一种隔离
效果,可以用来进行身份校验
签名
签名即是对原始数据的一种封装,我们可以在aop编程中获取到签名内部的指定信息
首先去核心配置类里注册一下@EnableAspectJAutoProxy
开启aop
@Component
@Aspect
public class Service_10K_speedtest {
// 匹配业务层的所有方法
@Pointcut("execution(* cn.calendo.service.*Service.*(..))")
private void ServicePt() {
}
@Around("Service_10K_speedtest.ServicePt()")
public void runspeed(ProceedingJoinPoint pjp) throws Throwable {
//Signature是我们执行时候封装的签名,通过签名可以获取接口名与方法名
//获取执行签名信息
Signature signature = pjp.getSignature();
//通过签名获取执行类型(接口名)
String className = signature.getDeclaringTypeName();
//通过签名获取执行操作名称(方法名)
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行" + className + "." + methodName + "---->" + (end - start) + "ms");
}
}
AOP通知获取数据
//JoinPoint:用于描述切入点的对象,必须配置成通知方法中的第一个参数,可用于获取原始方法调用的参数
@Before("pt()")
//使用JoinPoint去获取原方法对象
public void before(JoinPoint jp) {
//getArgs去原方法里面拿数组形式的参数
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ...");
}
@After("pt()")
public void after(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("after advice ...");
}
百度网盘提取码实例
因为提取码是末尾带空格的,所以需要trim()
一下
我们先写好提取码的接收与匹配(假设是固定的root
)
我们在主函数类里面接收到提取码(他是带首尾空格的,因此会不匹配)
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ResourcesService resourcesService = ctx.getBean(ResourcesService.class);
boolean flag = resourcesService.openURL("http://pan.baidu.com/haha", " root ");
System.out.println(flag);
}
}
然后我们看下初始的执行流程
dao层里面用来匹配提取码
我们的目标很明确,就是去新建一个aop来对这个原始的业务进行增强
我们新建一个aop类
然后注册一下@Compnent
注解开发组件与@Aspect
AOP标签
然后再定义一个空函数作为锚点
public void servicePt() {
}
在其之上定位到切入点进行加强方法
@Pointcut("execution(boolean com.itheima.service.ResourcesService.openURL(..))")
接下来写我们的aop方法
//使用环绕型以及servicePt锚点
@Around("Trim_Checked.servicePt()")
//由于我们需要原始数据的返回值进行处理,所以需要用到pjp
public Object trimPwd(ProceedingJoinPoint pjp) throws Throwable {
//获取数组形式参数
Object[] args = pjp.getArgs();
//for循环对参数进行匹配和trim
for (int i = 0; i < args.length; i++) {
//判断参数是不是字符串
if (args[i].getClass().equals(String.class)) {
//将改变的内容重新放回去
args[i] = args[i].toString().trim();
}
}
//作为object返回出去即可
Object ret = pjp.proceed(args);
return ret;
}
如果有相同的大量的业务需要同时添加的话,那么AOP将是不二之选!