aop详解与实战
1. 什么是AOP
aop:面向切面编程。采用横向机制。
oop:面向对象编程。采用纵向机制。
AOP,面向切面编程。就是通过某个切入点(比如方法开始、结束)向某个切面(被切的对象)切入环绕通知(需要切入的逻辑代码)。
比如一个类中的所有方法执行前都需要打印日志,那么可以通过AOP的方式来统一实现,而不需要在每个方法中都加入打印日志的代码逻辑。
2. AOP的常用使用场景
- 日志记录
- 权限控制
- 事物管理
- 缓存处理
...
3. AOP的实现方式
- Spring AOP
a) JDK 动态代理
b) Cglib 动态代理 - AspectJ
4. AspectJ是什么
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
Spring2.0以后新增了对AspectJ的全面支持。
常用注解:
@Before 前置通知 使用时需要指定一个value属性值,用于指定一个切入点表达式。【可以是现写的表达式,也阔以是已有的】。
@AfterReturning 后置通知 使用时需要指定pointcut/value属性值,用于指定切入点表达式。还能使用returning属性指定一个形参,该形参可用于访问目标方法的返回值。
@Around 环绕通知 使用时需要指定一个value属性值,用于指定一个切入点表达式。
@AfterThrowing 异常通知 使用时需要指定pointcut/value属性值,用于指定切入点表达式。还能使用throwing属性指定一个形参,该形参可用于访问目标方法抛出的异常。
@After 最终final通知,不管是否异常,该通知都会执行 使用时需要指定一个value属性值,用于指定一个切入点表达式。
@DeclareParents 引介通知
@Aspect 用在类上,表示当前类为一个切面类
在通知中可以使用了 JoinPoint 接口及其子接口 ProceedingJoinPoint 作为参数来获得目标对象的————类名,方法名,方法参数等。
★环绕通知必须接受一个类型为 ProceedingJoinPoint 的参数, 返回值也必须是 Object 类型,且需要抛出异常。返回值即为目标方法的返回值★
★异常通知可以传入Throwable类型的参数 来接收异常信息★
切入点表达式:
格式:execution( * com.example.aop.*.*(..))
execution:就是表达式的主体。
第一个*:返回类型,可以用代表所有类型
com.example.aop:表示需要拦截的路径包名
第二个*:类名,可以用代表所有类
第三个*:方法名,可以用*表示所有方法
(..):..表示任意参数
还支持通配符的使用:
1) *:匹配所有字符
2) ..:一般用于匹配多个包,多个参数
3) +:表示类及其子类
4)运算符有:&&,||,! AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。
除了上述表达式写法,aop还支持如下的几种写法:
参考
@Around("within(类型表达式)")
within(cn.javass..*) cn.javass包及子包下的任何方法执行
within(cn.javass..IPointcutService+) cn.javass包或所有子包下IPointcutService类型及子类型的任何方法
within(@cn.javass..Secure *) 持有cn.javass..Secure注解的任何类型的任何方法,必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
@Around("@within(注解类型))
@within cn.javass.spring.chapter6.Secure) 任何目标对象对应的类型持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
@Around("target(类型全限定名)")
target(cn.javass.spring.chapter6.service.IPointcutService) 当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法
target(cn.javass.spring.chapter6.service.IIntroductionService) 当前目标对象(非AOP对象) 实现了IIntroductionService 接口的任何方法不可能是引入接口
@Around("@target(注解类型)")
@target (cn.javass.spring.chapter6.Secure) 任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
@Around("args(参数类型列表)")
args (java.io.Serializable,..) 任何一个以接受“传入参数类型为 java.io.Serializable” 开头,且其后可跟任意个任意类型的参数的方法执行,args指定的参数类型是在运行时动态匹配的
@Around("@args(注解列表)")
@args (cn.javass.spring.chapter6.Secure) 任何一个只接受一个参数的方法,且方法运行时传入的参数持有注解 cn.javass.spring.chapter6.Secure;动态切入点,类似于arg指示符;
@Around("@annotation(注解类型)")
@annotation(cn.javass.spring.chapter6.Secure ) 当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配
@Around("this(类型全限定名)")
this(cn.javass.spring.chapter6.service.IPointcutService) 当前AOP对象实现了 IPointcutService接口的任何方法
this(cn.javass.spring.chapter6.service.IIntroductionService) 当前AOP对象实现了 IIntroductionService接口的任何方法也可能是引入接口
关于JoinPoint对象介绍:
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。
补充aop相关概念:
连接点(Join point)
连接点是在应用执行过程中能够插入切面的一个点。
切点(Pointcut)
一个切面并不需要通知应用的所有连接点,切点有助于缩小切面所通知的连接点范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。
因此,切点其实就是定义了需要执行在哪些连接点上执行通知。
切面(Aspect)
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和在何处完成其功能。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。
public interface JoinPoint {
String toString(); //连接点所在位置的相关信息
String toShortString(); //连接点所在位置的简短相关信息
String toLongString(); //连接点所在位置的全部相关信息
Object getThis(); //返回AOP代理对象
Object getTarget(); //返回目标对象
Object[] getArgs(); //返回被通知方法参数列表
Signature getSignature(); //返回当前连接点签名
SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
String getKind(); //连接点类型
StaticPart getStaticPart(); //返回连接点静态部分
}
关于ProceedingJoinPoint对象介绍:
ProceedingJoinPoint对象是JoinPoint的子接口。
主要多了如下两个方法:
Object proceed() throws Throwable //执行目标方法
Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法
关于Signature对象介绍:
Signature对象:是一个接口。用于获取或记录有关连接点的反射信息。他的子接口还提供了很多额外的实用方法。
常用子接口如下:
UnlockSignature 实现类 UnlockSignatureImpl
LockSignature 实现类 LockSignatureImpl
CatchClauseSignature 实现类 CatchClauseSignatureImpl
MemberSignature
子接口 FieldSignature 实现类 FieldSignatureImpl
子接口 CodeSignature[它还有很多子接口] 实现类 CodeSignatureImpl
常用方法如下:
String toString();
String toShortString(); //返回此签名的字符串缩写形式
String toLongString(); //返回此签名的字符串扩展形式
String getName(); //返回此签名的方法【即返回方法名】
int getModifiers(); //以整数形式返回此签名方法的修饰符。
Class getDeclaringType(); //返回一个java.lang.Class对象,该对象表示声明此成员的类或接口。
String getDeclaringTypeName(); //返回声明类型的全限定名称
关于签名咋个理解看下面的代码
5. SpringBoot整合AspectJ实现日志记录
★也可以AspectJ+自定义注解来做,这里没有展示这种★
5.1 导入依赖
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
5.2 定义切面类
① 在类上使用 @Component 注解 把切面类加入到IOC容器中
② 在类上使用 @Aspect 注解 使之成为切面类
@Aspect
@Component
public class LogAspect {
private Logger logger = LoggerFactory.getLogger(LogAspect.class);
/**
* 定义切入点,切入点为com.jsy.community下的函数
*/
@Pointcut("execution( * com.lihao.community..*.*(..)) && !execution(* com.lihao.community.intercepter..*.*(..)) && !execution(* com.lihao.community.exception..*.*(..))")
public void webLog() {
}
/**
* 前置通知:在连接点之前执行的通知
*
* @param joinPoint
* @throws Throwable
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ");
Calendar ca = Calendar.getInstance();
String time = df.format(ca.getTime());
logger.info("");
logger.info("访问时间 : " + time);
logger.info("访问路径 : " + request.getRequestURL().toString());
logger.info("请求方式 : " + request.getMethod());
logger.info("访问方法 : " + joinPoint.getSignature().getName()); //签名?
logger.info("访问IP : " + request.getRemoteAddr());
logger.info("方法参数 : " + Arrays.toString(joinPoint.getArgs()));
}
}
/**
* @return void
* @Author lihao
* @Description 后置通知
* @Date 2021/1/19 17:09
* @Param [ret]
**/
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
logger.info("返回结果 : " + ret);
}
/**
* @return void
* @Author lihao
* @Description 异常通知
* @Date 2021/1/19 17:43
* @Param [joinPoint, ex]
**/
@AfterThrowing(pointcut = "webLog()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
logger.info("异常信息 : " + methodName + "() 出现了异常——————" + ex.getMessage());
}
}
6 SpringBoot整合AspectJ实现缓存[利用aop做延时双删]
实现原理:
热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存,毕竟强大到冒泡的QPS和极强的稳定性不是所有类似工具都有的,而且相比于memcached还提供了丰富的数据类型可以使用,另外,内存中的数据也提供了AOF和RDB等持久化机制可以选择,要冷、热的还是忽冷忽热的都可选。
结合具体应用需要注意一下:很多人用spring的AOP来构建redis缓存的自动生产和清除,过程可能如下:
Select 数据库前查询redis,有的话使用redis数据,放弃select 数据库,没有的话,select 数据库,然后将数据插入redis
update或者delete数据库钱,查询redis是否存在该数据,存在的话先删除redis中数据,然后再update或者delete数据库中的数据
上面这种操作,如果并发量很小的情况下基本没问题,但是高并发的情况请注意下面场景:
为了update先删掉了redis中的该数据,这时候另一个线程执行查询,发现redis中没有,瞬间执行了查询SQL,并且插入到redis中一条数据,回到刚才那个update语句,这个悲催的线程压根不知道刚才那个该死的select线程犯了一个弥天大错!于是这个redis中的错误数据就永远的存在了下去,直到下一个update或者delete。
aop详解与实战的更多相关文章
- Spring4 AOP详解
Spring4 AOP详解 第一章Spring 快速入门并没有对Spring4 的 AOP 做太多的描述,是因为AOP切面编程概念不好理解.所以这章主要从三个方面详解AOP:AOP简介(了解),基于注 ...
- 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理
Spring AOP详解 . JDK动态代理.CGLib动态代理 原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...
- Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(上)
前言 Android中绘图离不开的就是Canvas了,Canvas是一个庞大的知识体系,有Java层的,也有jni层深入到Framework.Canvas有许多的知识内容,构建了一个武器库一般,所谓十 ...
- Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)
LinearGradient 线性渐变渲染器 LinearGradient中文翻译过来就是线性渐变的意思.线性渐变通俗来讲就是给起点设置一个颜色值如#faf84d,终点设置一个颜色值如#CC423C, ...
- [Spring学习笔记 5 ] Spring AOP 详解1
知识点回顾:一.IOC容器---DI依赖注入:setter注入(属性注入)/构造子注入/字段注入(注解 )/接口注入 out Spring IOC容器的使用: A.完全使用XML文件来配置容器所要管理 ...
- AOP 详解
1. 需求:统计方法执行的性能情况(来源:<精通Spring 4.x>) // 性能监视类 PerformanceMonitor package com.noodles.proxy; pu ...
- Spring AOP详解(转载)所需要的包
上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...
- Spring AOP详解及简单应用
Spring AOP详解 一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...
- 转:Spring AOP详解
转:Spring AOP详解 一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...
随机推荐
- Kubernetes K8S之kube-prometheus概述与部署
Kubernetes K8S之kube-prometheus概述与部署 主机配置规划 服务器名称(hostname) 系统版本 配置 内网IP 外网IP(模拟) k8s-master CentOS7. ...
- mysql—make_set函数
使用格式:MAKE_SET(bits,str1,str2,-) 1 返回一个设定值(含子字符串分隔字符串","字符),在设置位的相应位的字符串.str1对应于位0,str2到第1位 ...
- Linux设置开机自动挂载镜像文件
1.将文件上传到服务器上(本例上传到/Data/software下) 2.挂载 mount -o loop /Data/software/rhel-server-7.6-x86_64-dvd.iso ...
- 避免用using包装DbContext【翻译】
EF和EF Core 的DbContext类实现IDisposable接口.因此,很多最佳编程实践中都建议你将它们放在一个using()块中.不幸的是,至少在Web应用程序中,这样做通常不是一个好主意 ...
- Vitis下载安装尝试
Vitis下载安装记录 一.下载安装 文章目录 一.下载安装 提示:以下是本篇文章正文内容,下面案例可供参考 一.下载安装 首先本次下载主要使用的是linux系统,所以我们先看一下Vitis支持的li ...
- 01-CentOS 8.1安装 Docker
官方参考地址:https://docs.docker.com/install/linux/docker-ce/centos/ 里面包含包下载地址:https://download.docker.com ...
- linux自定义安装位置安装jdk
注:本文系参考网络内容及本人实践得出 1 下载jdk安装包 下载地址:https://www.oracle.com/java/technologies/javase/javase-jdk8-downl ...
- 同一份数据,Redis为什么要存两次
前言 在 Redis 中,有一种数据类型,当在存储的时候会同时采用两种数据结构来进行分别存储,那么 Redis 为什么要这么做呢?这么做会造成同一份数据占用两倍空间吗? 五种基本类型之集合对象 Red ...
- Linux中让终端输入变为非阻塞的三种方法
介绍 在linux下每打开一个终端,系统自动的就打开了三个文件,它们的文件描述符分别为0,1,2,功能分别是"标准输入"."标准输出"和"标准错误输出 ...
- Linux日志文件(常见)及其功能
日志文件是重要的系统信息文件,其中记录了许多重要的系统事件,包括用户的登录信息.系统的启动信息.系统的安全信息.邮件相关信息.各种服务相关信息等.这些信息有些非常敏感,所以在 Linux 中这些日志文 ...