Spring之AOP理解及使用
文章目录
大家好,我是Leo!今天给大家带来的是关于Spring AOP的一些知识和动态代理的两种实现方式,以及AOP的使用,希望大家可以喜欢
AOP是什么
AOP:面向切面编程,简单的说,就是把一些业务逻辑中的相同代码抽取到一个独立的模块中,让业务逻辑更加清楚,耦合性更低。
AOP
切面(Aspect):描述的是一个应用系统的某一方面或领域,如日志、事务、权限检查等。切面和类非常相似,对连接点、切入点、通知及类型间声明进行封装。
连接点(Joinpoint):连接点是应用程序执行过程中插入切面的点,这些点可能是方法的调用、异常抛出或字段的修改。Spring只支持方法的Joinpoint,也就是Advice将方法执行的前后被应用
通知(Advice):表示切面的行为,具体表现位实现逻辑的一个方法,常见的方法有Before、After、Around和Throws。Before和After分别表示通知在连接点的前面或者后面执行,Around则表示在连接点的外面执行,并可以决定是否执行此连接点。Throws通知方法抛出异常时执行。
切入点(Pointcut):切入点指定了通知应用在哪些连接点上,Pointcut切点通过正则表达式定义方法集合。切入点由一系列切入点指示符通过逻辑运算组合得到,AspectJ的常用切入点指示符包括execution、call、inititalization、handler、get、set、this、target、args、within等
目标对象(Target): 目标对象是指被通知的对象,它是一个普通的业务对象,如果没有AOP,那么它其中可能包含打量的非核心业务逻辑代码,如日志、事务等;而如果使用了AOP,则其中只有核心的业务逻辑代码。
代理(Proxy):代理是指通知应用到目标对象后形成的新的对象。
织入(Weraving): 织入是指将切面应用到目标对象而建立一个新的代理对象的过程,切面在指定的接入点被织入目标对象中。
Spring的通知类型
1.Before通知
Before通知处理前,该方法还未执行,所以使Before通知无法返回目标方法的值
2. AfterReturning通知
在切点表达式中定义的方法后执行
pointcut/value:这两个属性是一样的,用来指定切入点对呀的切入点表达式,可以是已定义的切入点,也可直接定义切入点表达式
returning:指定一个返回值的形参名,可以通过该形参可访问目标方法的返回值
3.AfterThrowing通知
主要用来处理程序中未处理的异常。使用AfterThrowing通知可指定如下属性。
pointcut/value:用来指定切入点对应的切入点表达式
throwing:指定一个返回形参名,通过该形参访问目标方法抛出但未处理的异常。
4. After通知
与AfterReturning通知非常类似,但是AfterReturning通知只有在目标方法执行完毕才可以被织入
5. Around通知
这个功能比较强大,近似等于Before通知和AfterReturning通知的总合,但是Around通知可以决定目标方法什么时候执行,如何执行。
在定义Around通知的切面逻辑方法时,必须给方法加入ProceedingJoinPoint类型的参数,在方法内调用ProceedingJoinPoint的proceed()方法才会执行目标方法。
优先级由低到高的顺序
Before通知–》Around通知–》AterReturning通知 --》After通知 /AfterThrowing通知
动态代理
动态代理就是,在程序运行期创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。
Spring AOP是通过动态代理实现的,动态代理主要有两种方式JDK动态代理和CGLib动态代理,这两种动态代理的使用和原理有些不同。
JDK动态代理
基于接口的动态代理
对于JDK代理,目标类需要实现一个Interface
InvocationHandler是一个接口,可以实现这个接口,再通过调用反射机制(invoke)调用目标类的代码
Proxy可以使用newProxyInstance,传入类加载器,目标的接口,InvocationHandler,生成一个动态代理的对象
CGLib动态代理
基于类的动态代理
JDK代理的一大限制是只能为接口创建代理实例,CgLib没有这个限制
CgLib创建的动态代理对象性能比JDK性能要高
CgLib是基于ASM实现,通过字节码技术为一个类创建子类,在子类中采用方法拦截父类方法的调用
两种动态代理的图如下,可以很清楚看出JDK代理是需要实现接口,而CgLib不需要。

动态代理的代码展示
JDK动态代理
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy() {
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
/**
*
* @param proxy 代理对象
* @param method 代理需要实现的方法
* @param args 参数
* @return 返回值
* @throws Throwable 抛出的异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用目标的方法
Object result = null;
try {
System.out.println("[动态代理][日志] "+ method.getName()+",参数:"+ Arrays.toString(args));
result = method.invoke(target, args);
System.out.println("[动态代理][日志] "+ method.getName()+",结果:"+ result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[动态代理][日志] "+ method.getName()+",异常:"+ e.getMessage());
} finally {
System.out.println("[动态代理][日志] "+ method.getName()+",方法执行完毕");
}
return result;
}
};
/**
* Proxy.newProxyInstance
* 三个参数
* 1. 加载动态生成代理类来加载的类加载器
* 2. interfaces: 目标对象实现的所有接口class类型数组
* 3. InvocationHandler 设置代理对象实现对象方法的过程
*/
return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
}
}
CgLib动态代理
public class ProxyCglibFactory implements MethodInterceptor {
private Object target;
public ProxyCglibFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(target.getClass());
// 设置回调函数
enhancer.setCallback(this);
// 创建子类对象处理
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 调用目标的方法
Object result = null;
try {
System.out.println("[动态代理][日志] "+ method.getName()+",参数:"+ Arrays.toString(args));
result = method.invoke(target, args);
System.out.println("[动态代理][日志] "+ method.getName()+",结果:"+ result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[动态代理][日志] "+ method.getName()+",异常:"+ e.getMessage());
} finally {
System.out.println("[动态代理][日志] "+ method.getName()+",方法执行完毕");
}
return result;
}
}
AOP使用
AOP切面这些概念的定义是比较抽象的,下面我们来看一下如何使用。
假设一个场景,我有一个计算器类有加减乘除四个方法,我想要打印每一个方法的参数和对应的返回值。
有人就说啦,我每一个System.out.println,或者log.info不就行了嘛,但是加入有一百个方法呢,不断复制CV是不是多余的?
Calculator.java
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部 result = " + result);
return result;
}
}
切面类的配置
@Component
@Aspect
public class LogAspect {
/**
* 设置切入点和通知类型
* 切入点表达式 execution(访问修饰符 返回值类型 方法所在类的全路径 方法名 参数列表
* 通知类型:
* 前置@Before
* 返回@AfterReturning
* 异常@AfterThrowing
* 后置@After
* 环绕@Around()
*/
@Before(value = "pointCut()")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("Logger-->前置通知,方法名称:" + methodName + "参数:" + Arrays.toString(args));
}
@After(value = "pointCut()")
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名称:" + methodName);
}
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->返回前置通知,方法名称:" + methodName + "返回值:" + result.toString());
}
@AfterThrowing(value = "pointCut())", throwing = "exception")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable exception) {
String methodName = joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名称:" + methodName + "异常:" + exception.toString());
}
@Around(value = "pointCut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String argsString = joinPoint.getArgs().toString();
Object result = null;
try {
System.out.println("环绕通知");
// 调用目标方法
result = joinPoint.proceed();
System.out.println("环绕通知==目标方法返回值之后");
} catch (Throwable ex) {
System.out.println("环绕通知==目标方法出现异常之后");
} finally {
System.out.println("环绕通知==目标方法执行完毕");
}
return result;
}
/**
* 重用切入点表达式
*/
@Pointcut(value = "execution(* com.zly.aop.annotation.CalculatorImpl.* (..))")
public void pointCut() {
}
}

最后
希望各位大佬指正,点点
Spring之AOP理解及使用的更多相关文章
- spring ioc aop 理解
OC,依赖倒置的意思,所谓依赖,从程序的角度看,就是比如A要调用B的方法,那么A就依赖于B,反正A要用到B,则A依赖于B.所谓倒置,你必须理解如果不倒置,会怎么着,因为A必须要有B,才可以调用B,如果 ...
- Spring(DI,AOP) 理解(一)
感觉自己的spring理解的不好.所以重新开始学习. 这篇文章主要是来理解DI(依赖注入),Aop(切面) 一.DI(依赖注入,这里没有涉及到注释.只是用xml文件和Bean的方法来注册pojo,) ...
- Spring的Aop理解
主要作用:解决代码复用,避免重复性编写代码. 比较典型的场景:日志打印,权限验证,事务处理 参考网址为:http://moon-walker.iteye.com/blog/2381532 spring ...
- 9.秋招复习简单整理之Spring面试AOP和IOC的理解
1.Spring的AOP理解: OOP面向对象,允许开发者定义纵向的关系,但不适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用. AOP,一般称为面向切面,作为面向对象的一种补充,用 ...
- Spring的AOP简单理解
最近在研究spring的AOP,翻译出来的意思是面向切面. 总结如下: 所谓AOP就是将分散在各个方法处的公共代码提取到一处, 并通过类似拦截器的机制实现代码的动态整合.可以简单地想象成, 在某个方法 ...
- 对于Spring中AOP,DI,IoC概念的理解
IOC IoC(inversion of Control),控制反转.就好像敏捷开发和SCRUM一样,不是什么技术,而是一种方法论,一种工程化的思想.使用IoC的思想意味着你将设计好的对象交给容器控制 ...
- 七、Spring之深入理解AOP源码
Spring之深入理解AOP源码 在上一篇博文中,我们对AOP有了初步的了解,那么接下来我们就对AOP的实现原理进行深入的分析. 在之前写的那个AOP示例代码当中有这样一个注解:@Enable ...
- Spring里面AOP的和IOC的理解
1.Spring里面AOP的理解:https://www.cnblogs.com/hongwz/p/5764917.html例如日志功能.日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心 ...
- spring的AOP个人理解和使用
1什么是AOP:AOP是面向切面编程,也就是说面向某个功能模块编程,典型的应用就是Spring的声明式事务, Spring的AOP事务解析: 在以前的事务管理是要融合在逻辑代码中的,在逻辑代码中决定事 ...
- spring AOP理解和相关术语
一.AOP理解 AOP:横向抽取机制,底层使用代理方式实现. 示例: 现有LogDAO接口以及实现Log接口的Log类.类有add的方法,现在要打印add方法的开始时间和结束时间.(即增强Log的ad ...
随机推荐
- LVS简略介绍
一.lvs是什么 LVS是 Linux Virtual Server 的简称,也就是Linux虚拟服务器.这是一个由章文嵩博士发起的一个开源项目,它的官方网站是 http://www.linuxvir ...
- TCP idle timeout 和TCP Keepalive 比较和分析
TCP idle timeout 和TCP Keepalive 是两个独立的功能. TCP idle timeout TCP idle timeout 是系统TCP配置文件中的空闲超时设 ...
- “你帮我助”软件开发(Final)
本项目是上海交通大学 CS-3331 软件工程课程大作业. 作业描述 "你帮我助"软件开发(Final) 新的功能需求: 物品有公共的信息(物品名称,物品说明,物品所在地址,联系人 ...
- 【picoCTF】GET aHEADwrite up
打开链接,页面如下: 这道题我试了两种解法,大家都可以看看哦! 一.burpsuit拦截 1.点击bule,打开burpsuit拦截(记得打开intercept哦) 2.将 POST 请求更改为 HE ...
- mysql 5.7启动报错
mysql 5.7 yum 安装完启动报错,如图: 处理步骤:查看/etc/my.cnf 数据存放目录,将里面内容移除到/opt后,启动mysql正常.
- CSS 平滑滚动 scroll-behavior: smooth
凡是需要滚动的地方都加一句scroll-behavior:smooth 来提升滚动体验! 经常使用的锚点定位功能就有了平滑定位功能,如<a href="#">返回顶部& ...
- 换个脑袋,做个小练习------四则运算系统的随机出题的jsp实现
四则运算出题系统网页界面的实现(别期待,只有俩操作数) index.jsp <%@ page contentType="text/html;charset=UTF-8" la ...
- 2023 年最新最全的 React 面试题
React 作为前端使用最多的框架,必然是面试的重点.我们接下来主要从 React 的使用方式.源码层面和周边生态(如 redux, react-router 等)等几个方便来进行总结. 1. 使用方 ...
- Dcat admin 多文件上传,七牛云云端上传
进入官网 Dcat Admin - Php后台开发框架 这里要选择1.x 下面来安装框架 安装完laravel之后,需要修改.env文件,设置数据库链接设置正确 安装 dcat-admin comp ...
- 【备忘录】 主定理 Master Theorem (转载)
备忘录 https://zhuanlan.zhihu.com/p/113406812