目录
基础概念
实例代码,可以先不看
package com.scm.myblog.aop;
import com.scm.myblog.utils.CharsetFilterUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class CharsetAspect {
//定义字符过滤切点
@Pointcut("execution(* com.scm.myblog.controller.ArticleController.getArticleDetail(..))")
public void CharSetPoint(){}
//字符过滤环绕切面
@Around("CharSetPoint()")
public Object CharsetAspects(ProceedingJoinPoint pjp){
//获取所有请求参数
Object[] args = pjp.getArgs();
//过滤请求参数
args[0]= CharsetFilterUtils.tranCharset((String) args[0]);
//将结果放回pjp
Object o;
try {
o= pjp.proceed(args);
} catch (Throwable e) {
throw new RuntimeException(e);
}
//返回改变后的object
return o;
}
}
@ApiOperation("模糊查询加分页获取文章数据")
@PostMapping("/getPageData")
public Result getArticleByPageAndSearch(@RequestBody PageDto<Article, ArticleSearchDto> pageData) {
return articleService.getArticlePage(pageData,false);
}
@ApiOperation("获取详细文章内容")
@GetMapping("/getArticles/{title}")
public Result getArticleDetail(@PathVariable("title") String title, HttpServletRequest request) {
//将文章标题放在session中备用
WebUtils.setValBySession(request,"title",title);
return articleService.getArticleData(title);
}
/**
* 获取文章归档
*
*/
@ApiOperation("根据时间线获取文章")
@GetMapping ("/getArticlesByTimeLine")
public Result getArticlesByTimeLine(){
return articleService.getArticlesByTimeLine();
}
连接点:因为Spring只支持方法执行作为连接点,所有在spring中的连接点就是满足条件的各个方法。看如下的图中的箭头中指向的方法即是连接点。
切点:切点就是我们配置的满足我们条件的目标方法。切点一定是连接点。它是连接点位置的具体化就是了,看图。
我们通过切点表达式配置了一个具体的连接点(getArticleDetail(..)),它是一个具体的方法,可以对比上上图中的连接点,加深理解。
通知/增强(Advice): 就是我们编写的通过Aop去实现代码增强的方法。看图即可,图中的方框中即是一个通知,它通过@Around实现了对切点处的方法的增强。
切面:切面是切点和通知的组合称谓,就是变相给组合起了个名字,还是看图即可,方框中的就是一个切面。
注意的是一个类中可能会有多个切面的。比如看代码即可。
我的代码中就出现了5个切面的。
package com.scm.myblog.aop;
import com.scm.myblog.entity.Comment;
import com.scm.myblog.entity.VO.Result;
import com.scm.myblog.serviceUtils.AdminBlogUtils;
import com.scm.myblog.serviceUtils.RedisServiceBox;
import com.scm.myblog.serviceUtils.UserBlogUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* web数据记录AOP
*
* @author Lancer
* @date 2022/12/09
*/
@Aspect
@Component
@Slf4j
public class WebDataRecordAspect {
@Autowired
public RedisTemplate<String,String> rs;
//设置浏览量增加切点
@Pointcut("execution(public * com.scm.myblog.controller.ArticleController.getArticleDetail(..))")
public void ViewAddPointcut() {}
//设置文章增加切点
@Pointcut("execution(public * com.scm.myblog.controller.AdminArticleController.addArticles(..))")
public void AddArticlePointcut() {}
//设置文章减少切点
@Pointcut("execution(public * com.scm.myblog.controller.AdminArticleController.removeArticles(..))")
public void RemoveArticlePointcut() {}
//设置评论增加切点
@Pointcut("execution(public * com.scm.myblog.controller.CommentController.setComment(..))")
public void AddCommentPointcut() {}
//设置评论减少切点
@Pointcut("execution(public * com.scm.myblog.controller.AdminCommentController.removeComment(..))")
public void RemoveCommentPointcut() {}
/**
* 浏览量增加切面
*
* @param j j
*/
@Before("ViewAddPointcut()")
public void ViewAdd(JoinPoint j){
String sendTitle="";
Object[] args = j.getArgs();
if (args != null) {
sendTitle=(String)args[0];
}
RedisServiceBox.addViewCount(sendTitle);
log.info(sendTitle+"浏览加1");
}
//文章DP增加到redis
@AfterReturning(pointcut ="AddArticlePointcut()",returning = "r")
public void AddArticle(Object r){
//获取方法返回值
Object data=getResultData(r);
if(data!=null){
String title=(String)data;
//增加根据文章的点赞数据到redis
RedisServiceBox.addDataToRedis(title);
log.info("新增文章成功!文章标题为 "+title);
}
}
//文章DP减少redis
@AfterReturning(pointcut ="RemoveArticlePointcut()",returning = "r")
public void RemoveArticle(Object r){
//获取方法返回值
Object data=getResultData(r);
if(data!=null){
Long[] ids=(Long[])data;
//删除文章的点赞数据在redis
RedisServiceBox.deleteBatchDataToRedis(ids);
log.info("删除文章成功!文章id为 "+ Arrays.toString(ids));
}
}
//评论量增加在redis
@AfterReturning(pointcut ="AddCommentPointcut()",returning = "r")
public void AddComment(Object r){
//获取方法返回值
Object data=getResultData(r);
if(data!=null){
String title=(String)data;
//更新Redis中的文章数据
RedisServiceBox.addCommentCount(title);
log.info("评论增加成功!文章标题为 "+title);
}
}
//评论量减少在redis
@AfterReturning(pointcut ="RemoveCommentPointcut()",returning = "r")
public void RemoveComment(Object r){
//获取方法返回值
Object data=getResultData(r);
if(data!=null){
List<Comment> commentList = (List<Comment>) (data);
List<Long> ids = AdminBlogUtils.getArticleIdByCommentList(commentList);
//更新Redis中的文章数据
RedisServiceBox.removeCommentCount(ids);
log.info("评论删除成功!文章id为"+ids);
}
}
public Object getResultData(Object r){
//获取方法返回值
Result r1 = (Result) r;
return r1.getData();
}
}
织入(Weaving):织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期、类装载期、运行期进行。也是创建AOP代理对象的过程。
Spring AOP 通知类型
AOP联盟定义的通知类型
通知类型 | 接口 | 描述 |
---|---|---|
前置通知 | org.springframework.aop.MethodBeforeAdvice | 在目标方法执行前实施增强。 |
后置通知 | org.springframework.aop.AfterAdvice | 在目标方法执行后实施增强。 |
后置返回通知 | org.springframework.aop.AfterReturningAdvice | 在目标方法执行完成,并返回一个返回值后实施增强。 |
环绕通知 | org.aopalliance.intercept.MethodInterceptor | 在目标方法执行前后实施增强。 |
异常通知 | org.springframework.aop.ThrowsAdvice | 在方法抛出异常后实施增强。 |
引入通知 | org.springframework.aop.IntroductionInterceptor | 在目标类中添加一些新的方法和属性。 |
AspectJ定义的通知类型
Before 前置通知:目标对象的方法调用之前触发
After 后置通知:目标对象的方法调用之后触发
AfterReturning 返回通知:目标对象的方法调用完成,在返回结果值之后触发
AfterThrowing 异常通知:目标对象的方法运行中抛出 / 触发异常后触发
注意一点,AfterReturning 与 AfterThrowing 两者是互斥的!如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
Around 环绕通知:编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
面试题
讲一下spring中的AOP
AOP 即面向切面编程,全称 Aspect Oriented Programming ,它是 OOP 的补充。OOP 关注的核心是对象,AOP 的核心是切面(Aspect)。AOP 可以在不修改功能代码本身的前提下,使用运行时动态代理的技术对已有代码逻辑增强。AOP 可以实现组件化、可插拔式的功能扩展,通过简单配置即可将功能增强到指定的切入点。在开发中我常使用的是AspectJ提供的通知类型,实现代码的增强的,然后AOP 的使用范围还是非常广的比如
1 业务日志切面:可以记录业务方法调用的痕迹
2 事务控制:通过切面可以声明式控制事务
3 权限校验:执行方法之前先校验当前登录用户是否有权调用
4 数据缓存:执行方法之前先从缓存中取,取到则直接返回不走业务方法。
讲一下如可控制切面的执行顺序
1 显式使用 @Order 注解,或者 Ordered 接口,声明切面的执行顺序(默认 Integer.MAX_VALUE ),即值越小的先执行,值越大的后执行,同时先执行的会后结束
2 通过使用类名的 unicode 编码顺序,控制切面的执行顺序,unicode越小的越先执行的
讲一下jdk动态代理和Cglib动态代理的对比
jdk 动态代理要求被代理的对象所属类至少实现一个接口,它是 jdk 内置的机制
Cglib 动态代理无此限制,使用字节码增强技术实现,需要依赖第三方 Cglib 包
jdk 动态代理的代理对象创建速度快,执行速度慢;
Cglib 动态代理的代理对象创建速度慢,执行速度快