前言
在项目中的使用,这里根据项目编写,项目正在全面开发中,项目中的相关技术我会不定时更新出来。在时机成熟时,会将项目开源以供大家共同研究。
简介
AOP(Aspect Oriented Program):面向切面编程-通过预编译方式和动态代理实现程序功能的统一维护的一种技术。降低业务逻辑部分的耦合度,提高程序的可重用性,提高开发效率。
从表面上来说,减少了代码的拷贝和对公共方法的显示调用,使用aop利用切面的方式实现公共方法的调用。
概念
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
相关术语
术语 | 解释 |
---|---|
Pointcut 切入点 | 对JointPoint进行拦截的定义即为切入点。 |
JointPoint连接点 | 类中可以被增强的方法即为连接点 |
Advice 通知 | 拦截到JoinPoint之后要做的事情即通知,分前置,后置,异常,返回,环绕通知 |
Aspect 切面 | Pointcut和Advice的组合。 |
Target(目标) | 要增强的类。 |
Weaving(织入) | 将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程 |
图解:
示例
使用的是springboot,很多配置就简单很多。
- 引入依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.4.4</version>
</dependency>
2.编写配置类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class AspectAopConfig {
/**
* 切入点
* 匹配 包及其子包下的所有类的所有方法
*/
@Pointcut("execution(* com.lvwenguo.controller.*.*(..))")
public void pointCut(){
}
/**
* 前置通知,目标方法调用前被调用
*/
@Before("pointCut()")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("----------- 前置通知 -----------");
Signature signature = joinPoint.getSignature();
System.out.println("返回目标方法的签名:" + signature);
System.out.println("代理的是哪一个方法:" + signature.getName());
Object[] args = joinPoint.getArgs();
System.out.println("获取目标方法的参数信息:" + Arrays.asList(args));
}
/**
* 最终通知,目标方法执行完之后执行
*/
@After("pointCut()")
public void afterAdvice(){
System.out.println("----------- 最终通知 -----------");
}
/**
* 后置返回通知
* 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* returning 只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行
* @param joinPoint
* @param keys
*/
@AfterReturning(value = "execution(* com.lvwenguo.controller.*.*(..))", returning = "keys")
public void afterReturningAdvice(JoinPoint joinPoint, String keys){
System.out.println("----------- 后置返回通知 -----------");
System.out.println("后置返回通知的返回值:" + keys);
}
/**
* 后置异常通知
* 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
* throwing 只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowingAdvice(JoinPoint joinPoint, NullPointerException e){
System.out.println("----------- 后置异常通知 -----------");
}
/**
* 环绕通知
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
* @param proceedingJoinPoint
*/
/* @Around("execution(* com.lvwenguo.user.controller.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("----------- 环绕通知 -----------");
System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
try {
Object proceed = proceedingJoinPoint.proceed();
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
System.out.println("---------- 环绕通知结束 -------------");
}
return null;
}*/
}
具体方法和使用已经标注的很详细,仔细品味以下还是很好理解的!
细节
这里要讲解一下关于切入点表达式的用法:
/**
* 切入点
* 匹配 包及其子包下的所有类的所有方法
*/
@Pointcut("execution(* com.lvwenguo.controller.*.*(..))")
public void pointCut(){
}
表达式:execution( com.lvwenguo.service.impl….(…))*
符号 | 含义 |
---|---|
execution() | 表达式的主体; |
第一个”*“符号 | 表示返回值的类型任意; |
com.lvwenguo.service.impl | AOP所切的服务的包名,即,我们的业务部分 |
包名后面的”…“ | 表示当前包及子包 |
第二个”*“ | 表示类名,*即所有类。此处可以自定义,下文有举例 |
.*(…) | 表示任何方法名,括号表示参数,两个点表示任何参数类型 |
这样使用其实也是有局限性的,如果你的项目很庞大,针对性的实现,诸如此类。
使用自定义注解实现:
import java.lang.annotation.*;
/**
* @author jushisi
* @description 自定义注解用于AOP拦截标识
*/
@Retention(RetentionPolicy.RUNTIME) // 表示注解在运行时依然存在
@Target(ElementType.METHOD)
@Documented
public @interface AopLog {
}
注解读取:
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSON;
/**
* @author jushisi
* @description TODO AOP为啥不能成功调用所有方法 有点奇怪 待研究
*/
@Component
@Aspect
public class HttpAspect {
private static final Logger logger = LoggerFactory.getLogger(HttpAspect.class);
@annotation(com.example.demo.aop.AopLog)")
@Pointcut("@annotation(com.example.demo.aop.AopLog)")
public void log(){
}
/**
* @description 注意使用ProceedingJoinPoint proceed()
* @author jushisi
*/
@Around("log()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("@Around:在建议方法调用之前和之后,执行通知。");
joinPoint.proceed();
logger.info("@Around:在建议方法调用之前和之后,执行通知。");
}
/**
* @description 没有环绕通知时 调用
* @author jushisi
*/
@Before("log()")
public void before(JoinPoint joinPoint){
ServletRequestAttributes attributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request =attributes.getRequest();
logger.info("@Before:在一个方法执行之前,执行通知。");
}
/**
* @description 没有环绕通知时 调用
* @author jushisi
*/
@After("log()")
public void after(){
logger.info("@After:在一个方法执行之后,不考虑其结果,执行通知。");
}
/**
* @description 没有环绕通知时 方法执行成功时调用
* @author jushisi
*/
@AfterReturning(pointcut = "log()",returning = "obj")
public void afterReturning(Object obj){
logger.info("@AfterReturning:在一个方法执行之后,只有在方法成功完成时,才能执行通知。");
logger.info("@AfterReturning:"+ JSON.toJSONString(obj));
}
/**
* @description 没有环绕通知时,抛异常时调用 ; 有环绕通知时,即使抛异常也不调用
* @author jushisi
*/
@AfterThrowing(pointcut = "log()", throwing = "ex")
public void afterThrowing(Exception ex){
logger.info("@AfterThrowing:在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。");
}
}
在你想要使用的方法上标识:
@RequestMapping("/test")
@AopLog
public String test(){
String str = "AOP function test ......";
logger.info(str);
return str;
}
到这里就实现了,是不是很简单!