「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」
AOP的出现主要是为了解决外围业务代码和核心业务代码分离的问题,例如:事务、日志记录、链路追踪等
aop的两种实现
- aspectJ (静态织入)
AspectJ采用的是静态织入的方式,在编译期使用 AspectJ 的 acj 编译器把 aspect 类编译成class字节码后,在java目标类编译时织入,即先编译 aspect 类再编译目标类
- spring-aop(动态织入)
spring-aop 采用的是基于运行时增强的代理技术,在运行时动态将要增强的代码织入到目标类中,实现技术主要有以下两种:
-
- JDK的动态代理:默认使用,底层通过反射技术实现,必须要有对应的接口类
- cglib的动态代理:采用字节码技术,为目标类创建一个子类,在子类中进行拦截,拦截父类方法调用并且织入切面逻辑
基于注解的spring Aop开发
示例代码
日志记录
/**
* @author chenw
* @date 2021/3/3 9:49
*
* 日志切面
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
//定义切入点
@Pointcut("execution(public * com.example.aop_demo.controller.*.*(..))")
public void weblog(){}
//前置通知,在连接点方法前调用
@Before("weblog()")
public void deBefore(JoinPoint joinPoint)throws Throwable{
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
log.info("URL:{}",request.getRequestURL().toString());
log.info("ARGS:{}", Arrays.toString(joinPoint.getArgs()));
}
//返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常
@AfterReturning(returning = "obj",pointcut = "weblog()")
public void doAfterReturning(Object obj)throws Throwable{
//方法返回值
log.info("返回值:{}",obj);
}
//异常通知,当连接点方法异常时通知
@AfterThrowing("weblog()")
public void afterThrow(JoinPoint joinPoint){
log.info("方法异常时执行");
}
//后置通知,在连接点方法后调用,不管有没有异常都会执行
@After("weblog()")
public void after(){
log.info("方法最后执行");
}
//环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法
@Around("weblog()")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
log.info("环绕通知");
try {
//执行目标方法
Object proceed = proceedingJoinPoint.proceed();
log.info("环绕执行结果:{}",proceed);
return proceed;
}catch (Throwable e){
e.printStackTrace();
return "哈哈哈哈";
}
}
}
Tips:使用 @Aspect 注解来定义这个为切面类,被定义为切面的类仍然是一个 bean ,需要使用 @Component 注解标注
切点指示符
通过 @Pointcut 注解进行定义,再通过 execution()中的正则表达式判断具体要拦截的是哪一个类哪一个方法
//定义规则
@Pointcut("execution(<访问权限> <返回类型> <方法名模式>(<参数模式>)<异常模式>)")
@Pointcut("execution(public * com.example.aop_demo.controller..*.*(..))")
| 通配符 | 含义 |
|---|---|
| .. | 匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包*//任意返回值,任意名称,任意参数的公共方法*execution(public * *(..)) |
| + | 匹配给定类的任意子类//匹配实现了DaoUser接口的所有子类的方法within(com.zejian.dao.DaoUser+) |
| * | 匹配任意数量的字符*//匹配com.zejian.service包及其子包中所有类的所有方法within(com.zeji an.service..)//匹配以set开头,参数为int类型,任意返回值的方法execution(* set*(int)) |
类型签名表达式(within)
为了方便类型(如接口、类名、包名)过滤方法,Spring AOP 提供了within关键字。其语法格式如下:
//type name 使用包名或者类名替换即可
within(<type name>)
//匹配com.zejian.dao包及其子包中所有类中的所有方法
@Pointcut("within(com.zejian.dao..*)")
//匹配UserDaoImpl类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl)")
//匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl+)")
//匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.zejian.dao.UserDao+)")
方法签名表达式(execution)
根据方法签名过滤
//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
//匹配UserDaoImpl类中的所有方法
@Pointcut("execution(* com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.zejian.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(int , ..))")
其他指示符
bean:Spring Aop扩展的,用于匹配特定名称的Bean对象的执行方法
//匹配名称中带有后缀Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}
this:用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
//匹配了任意实现了UserDao接口的代理对象的方法进行过滤
@Pointcut("this(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut2(){}
target:用于匹配当前目标对象类型的执行方法;
//匹配了任意实现了UserDao接口的目标对象的方法进行过滤
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut3(){}
@within: 用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行;
//匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("@within(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}
@annotation:根据所应用的注解进行方法过滤
//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}
Tips:切点指示符可以使用运算符语法进行表达式的混编,如 and 、 or 、 not 、 && 、 || 、 ! 等等
//匹配了任意实现了UserDao接口的目标对象的方法并且该接口不在com.zejian.dao包及其子包下
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao) !within(com.zejian.dao..*)")
private void myPointcut6(){}
//匹配了任意实现了UserDao接口的目标对象的方法并且该方法名称为addUser
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)&&execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut7(){}
通知类型
@Pointcut :定义连接点(切点)
- @Before :前置通知,在连接点方法前调用
@Around:环绕通知,它将覆盖原有的方法,但是允许你通过反射调用原有的方法@After:最终通知,在连接点方法后调用@AfterReturning:后置通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常@AfterThrowing:异常通知,当连接点方法异常时调用
前置通知(@Before)
Tips:JoinPoint:可以通过此静态变量获取到目标对象的信息,例如类名称、方法参数、方法名称等
//前置通知,在连接点方法前调用
@Before("weblog()")
public void deBefore(JoinPoint joinPoint)throws Throwable{
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
log.info("URL:{}",request.getRequestURL().toString());
log.info("ARGS:{}", Arrays.toString(joinPoint.getArgs()));
}
后置通知(@AfterReturning)
returning :声明一个接收切点方法执行后的返回值的变量
//后置通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常
@AfterReturning(returning = "obj",pointcut = "weblog()")
public void doAfterReturning(JoinPoint joinPoint,Object obj)throws Throwable{
//方法返回值
log.info("返回值:{}",obj);
}
异常通知(@afterThrowing)
throwing :声明一个接受异常信息的变量
//异常通知,当连接点方法异常时通知
@AfterThrowing(value = "weblog()",throwing = "throwable")
public void afterThrow(JoinPoint joinPoint,Throwable throwable){
log.info("方法异常时执行");
}
最终通知(@after)
类似与finally语句块,只要用了,不管什么情况下都会执行。
//最终通知,在连接点方法后调用,不管有没有异常都会执行
@After(value = "weblog()")
public void after(JoinPoint joinPoint){
log.info("方法最后执行");
}
环绕通知(@Around)
环绕通知可以在方法执行前执行,也可以在方法执行后执行,但是必须要手动去调用目标方法
//环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法
@Around(value = "weblog()")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
log.info("环绕通知");
try {
//执行目标方法
Object proceed = proceedingJoinPoint.proceed();
log.info("环绕执行结果:{}",proceed);
return proceed;
}catch (Throwable e){
e.printStackTrace();
return "哈哈哈哈";
}
}