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具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...
随机推荐
- python sqlite3增加表字段
给sqlite3表格增加新字段,要注意大小写,要不然不成功. 一开始这样写,不成功! 后面规范写,按大小写严格规范写! 成功了!现在查看新增加的字段commit: 仔细看,这下全部小写,括表名称.co ...
- DDIC_TYPELENG_INCONSISTENT错误的解决办法
当执行某个TCODE,例如SM66,出现类似如下的dump界面 大概意思就是说是ddic种的某个数据类型有问题,可能是数据结构,可能是数据元素或者是表等等 通过查阅资料了解到,对于note122290 ...
- ABAP-ALV-如何去掉OO方法中的ALV的标准按钮
SAP在做报表开发中,不同公司对报表的风格往往各异,为此经常在使用OO方法做ALV报表中需要去掉自带的工具栏而自行添加一些工具按钮,下面将简单介绍一些其实现过程与原理: 步骤一: DATA : gt_ ...
- CF76A Gift
题目描述 有一个国家有N个城市和M条道路,这些道路可能连接相同的城市,也有可能两个城市之间有多条道路. 有一天,有一伙强盗占领了这个国家的所有的道路.他们要求国王献给他们礼物,进而根据礼物的多少而放弃 ...
- Graph Explore的使用介绍
我在Graph API开发中用的最多的测试工具就是Graph Explore,这个是微软开发的网页版的Graph API的测试工具,能满足我大部分需求. 访问网址是:Graph Explorer - ...
- docker 运行时常见错误
docker 运行时常见错误 (1) Cannot connect to the Docker daemon at unix:///var/run/docker.sock. [root@localho ...
- Ubuntu源、Python虚拟环境及pip源配置
Ubuntu 命令行更改源 在修改source.list前,最好先备份一份 软件源的地址配置文件在 /etc/apt/sources.list 执行备份命令 sudo cp /etc/apt/sour ...
- C语言中二维数组声明时,探究省略第一维的原因
我们在使用二维数组作为参数时,我们既可以指明这个数组各个维度的维数,同时我们也可以省略一维,但是二维却不能省略.why呢?由于编译器原理的限制,在一个数组Elemtype test[m][n]中,访问 ...
- (15)-Python3之--configparser模块
1.模块简介 configparser模块是python用来读取配置文件的模块,置文件的格式跟windows下的ini或conf配置文件相似,可以包含一个或多个节(section), 每个节可以有多个 ...
- 开源AwaitableCompletionSource,用于取代TaskCompletionSource
1 TaskCompletionSource介绍 TaskCompletionSource提供创建未绑定到委托的任务,任务的状态由TaskCompletionSource上的方法显式控制,以支持未来的 ...