面向切面编程(Aspect-Oriented Programming,AOP)是一种编程思想,它可以帮助我们更好地实现代码复用和模块化。AOP的核心概念是“切面”,它是一种横向的关注点,可以跨越多个不同的对象和模块,提供额外的功能和服务。可以在不修改原始代码的情况下对功能做增强
切面(Aspect):跨越一个或多个对象的横切关注点,即某些需要进行统一处理的代码片段的集合。
连接点(Join Point):程序执行中可以插入切面的特定点,比如方法调用、对象初始化和异常处理等。
切点(Pointcut):用于定义一组连接点的规则,指定切面代码应该插入到哪些连接点中。
通知(Advice):在连接点处执行的代码,可以在连接点之前、之后或者之后抛出异常时执行。
引入(Introduction):用于向现有的类中添加新方法或属性的方式,比如向现有类中添加一个新的接口。
织入(Weaving):将切面代码插入到目标对象中,生成新的代理对象的过程。
目标对象(Target Object):包含连接点的对象,即需要被代理的对象。
AOP代理(AOP Proxy):在目标对象上应用切面之后生成的代理对象,它将请求转发给目标对象并在必要时执行切面代码。
1、导入坐标(pom.xml)
2、制作连接点(原始操作,Dao接口与实现类)
3、制作共性功能(通知类与通知)
4、定义切入点
5、绑定切入点与通知关系
1、导入坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
2、实现原始功能
定义一个bookDao类里面有两个方法
save方法执行会输出当前的系统时间,update执行不会输出当前系统时间
之后我们要把输出系统时间这个代码抽离出去,定义成一个单独的类
package com.example.demo.dao;
import org.springframework.stereotype.Component;
@Component
public class BookDao {
public void save(){
System.out.println(System.currentTimeMillis());
System.out.println("book dao save");
}
public void update(){
System.out.println("book dao update...");
}
}
3、定义通知类,绑定切入点与通知类的关系
package com.example.demo.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
@Aspect
public class MyAdvice {
@Pointcut("execution( void com.example.demo.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
在这个类中我们定义输出当前时间的方法
@Aspect 告诉spring这个类是一个切面
@Pointcut 定义切点,在上面的代码中,我们定义了当代码执行到void 没有返回值,且是com.example.demo.dao.BookDao.update()这个方法时才绑定切点与通知的关系
@Before 在方法执行前执行通知类中的方法
4、spring配置文件
package com.example.demo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy
public class SpringConfig {
}
@EnableAspectJAutoProxy 这个注解的意思是告诉程序我们要用注解开发程序
5、在App类中测试
package com.example.demo;
import com.example.demo.config.SpringConfig;
import com.example.demo.dao.BookDao;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@SpringBootApplication
public class App {
public static void main(String[] args) {
ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = app.getBean(BookDao.class);
bookDao.save();
bookDao.update();
}
}
右键运行输出的内容如下
可以看到两个方法前面都输出了当前的时间
execution( void com.example.demo.dao.BookDao.update())
关键词:描述切入点的行为动作,execution表示指定切入点
访问修饰符:public,private等,可以省略
返回值
包名
类/接口名
方法名
参数
execution( public * com.example.demo.dao.BookDao.*(*))
“ * “单个独立的任意符号,可以作为前缀或者后缀出现,表示匹配任意
execution( public void com.example..dao.BookDao..())
" … "连续的任意符号,可以独立出现,常用于简化包名
execution( public void com.example..dao+.*(..))
" + " 用于匹配子类类型
我们把案列中的切点表达式改成以下
@Pointcut("execution( * com.example..dao.BookDao.*())")
结果一样,不变
在AOP配置中,定义切点和切面。切点是一个或多个方法或类的集合,上面的案列中就是一个方法,输出当前时间
当程序运行到某个切点时,AOP框架会拦截这个切点,并根据配置的切面,执行一些预先定义好的方法
在切面中,可以使用不同类型的通知(Advice),例如:
前置通知(Before Advice):在切点之前执行的通知 ,@Before。
后置通知(After Advice):在切点之后执行的通知,@After。
环绕通知(Around Advice):在切点前后都可以执行的通知,可以控制方法执行的流程,@Around。
异常通知(After Throwing Advice):在切点抛出异常时执行的通知,@AfterThrowing。
返回通知(After Returning Advice):在切点返回结果时执行的通知,@AfterReturning。
在执行完切面逻辑后,AOP框架会继续执行原来的程序流程。
其中@Before、@After、@AfterThrowing和@AfterReturning注解需要指定切点表达式(Pointcut Expression)在上面的案列中execution( void com.example.demo.dao.BookDao.update()) 这句代码就表示切点表达式,表示哪些方法需要被切面拦截;
@Around注解除了需要指定切点表达式,还需要在方法中手动调用ProceedingJoinPoint对象的proceed()方法,以继续执行原方法。
在上面的案例中我们可以在通知类中添加一个Around通知
package com.example.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution( * com.example..dao.BookDao.*())")
private void pt(){}
@Pointcut("execution( * com.example..dao.BookDao.*())")
private void pt1(){}
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
@Around("pt1()")
public void method1(ProceedingJoinPoint pjp){
System.out.println("环绕通知开始");
try {
pjp.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
System.out.println("环绕通知结束");
}
}