深入理解下AOP(个人理解)
一、概念:
aop就是利用反射和代理模式实现动态代理
二、需求:
利用aop结合redis实现页面访问量的统计
不多BB直接上代码!
三、代码实现
1.自定义一个注解
/**
* 自定义注解:自定义一个注解作为切点
* 用于统计接口访问量
*/
@Retention(RetentionPolicy.RUNTIME)//运行时有效
@Target({ElementType.METHOD,ElementType.TYPE})//可以用在方法和类上
@Documented
public @interface CustomTrafficStatistics {
}
2.定义切面和切点以及代码需要进行环绕的代码
/**
* @Author xumin
* @Date 2022/5/15 20:55
*
* @Aspect:标识切面
* @Compoment:加入Spring-boot容器工工单
*
**/
@Aspect
@Component
public class CustomTrafficStatisticsAspect {
//配置日志输出
private final static Logger logger = LoggerFactory.getLogger(CustomTrafficStatisticsAspect.class);
/**
* 以自定义的@CustomTrafficStatistics注解为为切点
* 目前的理解是,AOP约定@Pointcut只能定义在一个方法上,然后他们反射出被这个方法标记的对象(方法、类什么的),用target代理出来然后对这个代理对象做一个前后操作
* 总之,感觉这个定义在方法上多此一举为啥不能直接定义在注解上呢。求大佬们解惑!
*/
@Pointcut("@annotation(org.jeecg.common.annotation.CustomTrafficStatistics)")
public void customTrafficStatistics(){};
/**
* 在切点之前做如下操作
* @param joinPoint
*/
@Before("customTrafficStatistics()")
public void doBefore(JoinPoint joinPoint) throws IOException, ClassNotFoundException {
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 打印请求相关参数
logger.info("========================================== Start ==========================================");
// 打印请求 url
logger.info("URL : {}", request.getRequestURL().toString());
// 打印 Http method
logger.info("HTTP Method : {}", request.getMethod());
// 打印调用 controller 的全路径以及执行方法
logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
// 打印请求的 IP
logger.info("IP : {}", request.getRemoteAddr());
// 打印请求入参
logger.info("Request Args : {}",new ObjectMapper().writeValueAsString(joinPoint.getArgs()));
// 动态代理应该就是动态反射
// 1.把所有被AOP标记的类的全路径获取到,(找到所有@Aspect切面,找到这个切面下面的所有切点)
// 2.根据全路径反射出来,但后通过反射出来的对象作为代理对象,拿到它的方法(AOP应该是约定@Pointcut只能定义在一个方法上,然后他们反射出被这个方法标记的对象(方法、类什么的))
// 3.在这个方法前后添加我们的操作
//自制AOP可能实现的方式@Aspect、@Pointcut
//spring工具类,可以获取指定路径下的全部类
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
//定义需要监听的包
String basePackage = "org.jeecg.modules.demo.test.controller";
String resourcePattern = "/*.class";
//CLASSPATH_ALL_URL_PREFIX:类路径中所有匹配资源的伪URL前缀:" classpath*:" 检索给定名称的所有匹配资源
//ClassUtils.convertClassNameToResourcePath:找到指定包名下的所有class,会将传入进去的路径的所有.替换为/
//最终路径模样:classpath*:org/jeecg/modules/demo/test/controller/*.class
String pattern = resourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(basePackage) + resourcePattern;
//获取指定路径下的类资源
Resource[] resources = resourcePatternResolver.getResources(pattern);
//MetadataReader 的工厂类:元数据读取工厂类,用来读取元数据
//构造将resourcePatternResolver转换一下赋值给属性metadataReaderCache
CachingMetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
for (Resource resource : resources) {
//元数据信息读取器(Metadata:元数据)——可以读取类、注解等的元数据信息
MetadataReader reader = readerFactory.getMetadataReader(resource);
//读取器调用读取类的元数据的方法,读取到类的元数据
ClassMetadata classMetadata = reader.getClassMetadata();
//类元数据读取器拿到类名,里面包含很判断,可以判断类是否是接口、抽象类、注解等
String className = classMetadata.getClassName();
//反射获取类的代理对象
Class<?> clazz = Class.forName(className);
//判断该类是否包含CustomTrafficStatistics注解
CustomTrafficStatistics annotation = clazz.getAnnotation(CustomTrafficStatistics.class);
if (annotation!=null) {
logger.info(clazz.getName()+":存在该注解");
}
//获取该类的下的所有方法,并判断是否被注解,如果被注解可以继续做逻辑操作
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
CustomTrafficStatistics methodAnnotation = method.getAnnotation(CustomTrafficStatistics.class);
if (methodAnnotation!=null) {
logger.info(method.getName()+":存在该注解");
}
}
//获取该类的下的所有属性,并判断是否被注解,如果被注解可以继续做逻辑操作
Field[] fields = clazz.getFields();
for (Field field : fields) {
CustomTrafficStatistics fieldAnnotation = field.getAnnotation(CustomTrafficStatistics.class);
if (fieldAnnotation!=null) {
logger.info(field.getName()+":存在该注解");
}
}
}
}
/**
* 在切点之后织入
* @throws Throwable
*/
@After("customTrafficStatistics()")
public void doAfter() throws Throwable {
// 结束后打个分隔线,方便查看
logger.info("=========================================== End ===========================================");
}
/**
* 环绕
*/
@Around("customTrafficStatistics()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//开始时间
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
// 打印出参
logger.info("Response Args : {}", new ObjectMapper().writeValueAsString(result));
// 执行耗时
logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
return result;
}
}
basePackage(路径):
3.接口实现
/**
* @Author xumin
* @Date 2022/5/15 21:29
**/
@Slf4j
@Api("aop测试类")
@RestController
@RequestMapping("aop-test")
public class AopTestController {
//遇到的小问题
//这里必须用@Autowired注入而不是new,因为RedisUtil类上标记了@Component注解放到交给Spring管理
//如想了解请看下图
@Autowired
private RedisUtil redisUtil;
@ApiOperation("自定义注解AOP")
@GetMapping("hello")
@CustomTrafficStatistics
public String hello(){
return "hello"+redisUtil.incr("aop-test-hello",1);
}
}
问题如下:
四、@Pointcut切点注解详解
表达式标签(10种)
- execution:用于匹配方法执行的连接点
- within:用于匹配指定类型内的方法执行
- this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也* 类型匹配
- target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
- args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
- @within:用于匹配所以持有指定注解类型内的方法
- @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
- @args:用于匹配当前执行的方法传入的参数持有指定注解的执行
- @annotation:用于匹配当前执行方法持有指定注解的方法
- bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法
10种标签组成了12种用法
1、execution
使用execution(方法表达式)匹配方法执行。 基本格式:
@Pointcut("execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)")
- 其中带 ?号的 modifiers-pattern?,declaring-type-pattern?,throws-pattern?是可选项
- ret-type-pattern,name-pattern, parameters-pattern是必选项
- modifier-pattern? 修饰符匹配,如public 表示匹配公有方法
- ret-type-pattern 返回值匹配,* 表示任何返回值,全路径的类名等
- declaring-type-pattern? 类路径匹配
- name-pattern 方法名匹配,* 代表所有,set*,代表以set开头的所有方法
- (param-pattern) 参数匹配,指定方法参数(声明的类型),(..)代表所有参数,( ,String)代表第一个参数为任何值,第 * 二个为String类型,(..,String)代表最后一个参数是String类型
- throws-pattern? 异常类型匹配
举例说明
-----------其他用法后续更新的时候加上!---------------
参考: