带你理解Spring AOP

AOP概述

  • 在我们的日常开发中,除了正常业务逻辑外,还可能经常会需要在业务逻辑的特定位置加入日志,以便于调试和问题分析。但是这种插入日志的逻辑和业务逻辑间并不存在连续性和依赖性,这种逻辑侵入随着项目的不断发展,会导致项目越来越来臃肿,同时也更加难以管理。为了解决这个问题,优秀的前辈们推出了AOP(面向切面编程)理念以及很多优秀的AOP框架,其中比较有代表性的就AspectJ,AspectJ通过扩展java语言实现了AOP框架。当然我们的spring框架也实现了自己的AOP框架(Spring AOP),并在spring 2.0后完美的集成了ApectJ。
  • AOP有很多种实现方式,在最早的时候AOP的实现还是通过静态代理的方式实现,例如AspectJ通过自己的ajc编译器在程序编译阶段编译生成静态代理类的Class文件以完成相关切面逻辑,不过这种方式不够灵活,每次有新的接口或逻辑需要使用切面功能都需要修改AspectJ中的切面配置,然后重新启动项目,无疑这是很烦的。随着技术的发展,我们有了更多更好的技术实现,这里介绍两只中常用AOP实现技术,也是我们spring中使用的:
    (1)jdk动态代理:
    java在jdk 1.3后引入了jdk动态代理,可以在运行期间为实现了某个接口的对象动态的是生成代理对象,它是在运行期间通过反射实现的,灵活性很好了,但是较编译生成Class文件的实现方式,性能差了些,另外所有需要进行代理的对象都必须要实现某个接口,这是动态代理最大的硬伤了。
    (2)cglib动态字节码增强:
    大家都知道,jvm的类加载器并不在乎class文件如何生成,只要是满足规范的class文件都能加载运行。一般我们的java应用程序都是通过javac编译生成class文件,但是只要我们满足class文件规范,我们完全可以使用cglib等字节码工具在程序运行期间动态的构建Class文件。在我们从本地的class文件加载类文件并构建对象时,可以使用cglib动态的生成该对象的代理对象。

静态代理与动态代理

AOP的实现是基于代理模式的,所以我们需要通过了解代理模式来学习AOP,接下来我来介绍下静态代理和动态代理的实现。
1. 静态代理:
我们可以看下下面的代码:

public interface Subject {String getName();
}public class RealSubject implements Subject {public String getName() {return ”东哥真帅!“;}
}public class Proxy implements Subject {private RealSubject realSubject;public Proxy(Subject realSubject){this.realSubject = realSubject;}public String getName() {return realSubject.getName() + ”东哥真是666!“;}
}public class Main {public static void main(String[] args) {Subject realSubject = new RealSubject();Subject proxy = new Proxy(realSubject);proxy. getName();
}

我们可以看到真的是灰常简单,代理对象和原始对象都实现了Subject,代理对象引用了原始对象,并在接口调用时利用了realSubject的getName()方法,只不过在realSubject返回数据的基础上加了些文字,没错这就是最简单的静态代理。但这种模式存在一个致命的问题:getName()函数可能并不仅仅只有Subject接口实现了,其他接口可能也实现了这个函数,例如我再有个Topic接口,它也有这个getName()方法。那么当我的切面需要切到所有实现getName的函数时,我们还需要在为Topic接口实现一套类似上面的代码,这要搞下去的话累都累死了,为了解决这个问题,我们引出了动态代理。我们接下来看下基于jdk和cglib实现的动态代理。

2. 动态代理:

(1)jdk动态代理代码:

public class RequestInvocationHandler implements InvocationHandler {private Object target;public RequestInvocationHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if(method.getName().equals("getName")){return method.invoke(target, args) + "东哥真是666!";}return null;}}Subject subject = (Subject) Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),new Class[]{Subject.class},new RequestInvocationHandler(new RealSubject()));subject.getName();Topic topic = (Topic) Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),new Class[]{Topic.class},new RequestInvocationHandler(new RealTopic()));topic.getName();

jdk动态代理的实现主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。我们可以看出jdk动态代理是靠实现特定接口,我们在平时开发中不可能所有的对象都实现特定的接口,cglib为我们解决了这个问题。

(2)cglib实现动态代理代码:

    public class RequestInvocationHandler implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {if (method.getName().equals("getName")) {return methodProxy.invokeSuper(o, objects) + "东哥真是666!";}return null;}}Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Subject.class);enhancer.setCallback(new RequestInvocationHandler());Subject proxy = (Subject) enhancer.create();proxy.getName();

我们可以看到cglib通过继承目标对象,并覆写父类方法来实现动态代理,通过字节码生成技术在运行期间动态的自动帮我们构建代理对象。由于cglib采用继承覆写父类方法实现,所以也存在一定局限,当父类的方法为final、static、private类型时,cglib无法对方法进行代理,只能直接调用目标对象的方法。

AOP组件介绍

在具体了解整个AOP的运行流程前,我们先来看下AOP中几个比较重要的组件,当然这些术语都是参照ApectJ中的概念。

组件名概念
joinpointAOP功能模块需要织入的地方, 也有人称之为系统执行点,在ApectJ中有很多执行点类型,例如方法调用、方法执行、字段设置、类初始化等,但是在Spring Aop目前仅支持方法调用类型的切入点,不过已经能够我们大部分的业务开发了。
pointcutpointcut就是joinpoint的表述方式,通过pointcut的表达式我们就可以寻找到满足条件joinpoint。
adviceadvice是切入逻辑的载体,简单的说就是我们要织入的逻辑代码,他有很多种类型,包括Before Advice、After Advice等等,不过我们最常用的还是Around Advice,它同时包含了其他几种的功能。
aspect(advisor)aspect是将joinpoint和pointcut模块化封装的AOP概念实体,在我们SpringAop中也称为advisor,且一般一个advisor仅包含一个joinpoint和一个pointcut。
weaverweaver就是织入器,负责将逻辑织入到joinpoint的具体执行者。
target object被织入的对象

Spring AOP织入原理

我们先来看下伪代码:

// 新建织入者weaver
ProxyFactory weaver = new ProxyFactory(new Executable());// 新建根据接口名匹配的advisor(aspect)
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
// 设置pointcut,在这里是需要织入的接口名,"execute"相当于pointcut
advisor.setMappedName("execute");
// 设置Advice,ExecutableAdvice中包含了代码逻辑
advisor.setAdvice(new ExecutableAdvice());// 将advisor传递给weaver
weaver.addAdvisor(advisor);
// weaver通过ProxyFactor获取代理类
Executable proxyObject = weaver.getProxy();proxyObject.execute();

上面的伪代码实际上已经揭示了AOP的完整流程:
(1)通过advisor包装pointcut和advice数据
(2)然后weaver利用advisor内的数据来获取代理对象
通过上面的逻辑分析,我们可以知道,对于SpringAOP的实现,最重要的有两点即pointcut和advice数据的获取代理对象的生成,那我们接下来就分为这两部分着重讲解下。

(一)pointcut和advice数据的获取

其实上面的伪代码是spring早期的实现方式,如上面代码所示,当时的pointcut只能是方法名或者对象类型(支持正则表达式),而advice需要通过实现特定的接口来实现,advisor也是通过继承特定的系统类实现的,最后还需要将bean的信息以xml的形式保存起来以支持IoC。
当然现在没那么麻烦了,我们看下目前我们的使用方式(基于注解的代理):

@Aspect
@Component
class Aspect {@Pointcut("@annotation(com.xxx.xxx.xxx)")public void servicePointcut() {}@Around("servicePointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {xxxxxxxxxxxxxxx;xxxxxxxxxxxxxxx;return xxx;}
}

现在我们有注解支持并且集成了AspectJ,可以使用AspectJ表达式,但底层的实现原理还是一样的。我们现在使用的weaver在注入到容器后,容器会自动寻找容器内所有标识了@Aspect的advisor(aspect)对象,获取到advisor(aspect)对象后,会通过反射获取@Pointcut内的表达式,然后通过AspectJ的类库解析生成pointcut对象,最后ProxyFactory便可以利用这些数据生成代理对象了,其实原理还是一样的,只是加了层封装,更加方便了我们的业务开发。

(二)代理对象的生成

接下来就是最重点的部分了,其实也很简单,需要大家有足够的IoC知识,可以看下我的IoC原理文章:深入了解spring IoC
在获取到了pointcut和advice数据后,weaver便开始了代理对象的构造了。我们在上面的代码中可以看到weaver的类型是ProxyFactory,ProxyFactory是最简单基本的一个织入器实现类,目前我们最经常使用的织入器实现类是AnnotationAwareAspectJAutoProxyCreator,不过实现原理都是一样的:都是基于BeanPostProcessor。我们在IoC原理中介绍过BeanPostProcessor,BeanPostProcessor可以干预Bean的实例化过程,它有点类似于责任链,任何一个bean的构建都需要经过这条链,只有执行完所有BeanPostProcessor后才会返回实例化的对象。AOP实现了BeanPostProcessor,他在目标对象实例化后利用反射或cglib生成了目标对象的动态代理对象(实现逻辑见上面:jdk动态代理代码实现/cglib实现动态代理),然后直接将代理对象返回给了使用方。这样使用方使用的便是经过动态代理后的对象,便实现了AOP的功能。很简单吧!🙃🙃🙃🙃

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/450938.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

10.20随笔

ES6 ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。 这种语言在万维网上应用广泛,它往往被称为JavaScript或JScript,但…

极客招募令!兄弟杯区块链极客竞技大赛在上海等您来战!

据悉,由国内首家区块链技术社区区块链兄弟主办,旺链科技、离子链、中国云体系产业创新战略联盟、无退社区、指旺金科等单位强力支持,HiBlock区块链社区、火球财经、布洛克财经、海豚区块链、区块网等百家技术社区和媒体通力合作的兄弟杯区块链…

Java中Web程序修改配置文件不重启服务器的方法

见:http://blog.sina.com.cn/s/blog_69398ed9010191jg.html 另:http://ekisstherain.iteye.com/blog/1701463 jrebel 、JavaRebel是什么,见另一博客:jrebel/JavaRebel 开发环境 1. JDK 2. MyEclipse 3. Tomcat 4. Struts2 5.…

ffmpeg-0.6.3 移植到 windows 开源代码

ffmpeg-0.6.3开源编码解码库,从linux下移植到windows vs2005,全部开源。 需要 Intel C Compile 和 开源的SDL库支持,由于 Intel C Compile支持C99语法,所以源代码改动很小很小。 主要的修改 1:添加了linux中有而wind…

一起唠唠分布式锁

(1)分布式锁和分布式事务的区别 1.分布式锁是在集群环境下,用来控制不同机器对全局共享资源的访问。 2.分布式事务是在集群环境下,用来保证全局事务的一致性,保证多个数据库的数据整体上能正确的从一个一致性状态转到…

luogu2577/bzoj1899 午餐 (贪心+dp)

首先,应该尽量让吃饭慢的排在前面,先按这个排个序 然后再来决定每个人到底去哪边 设f[i][j]是做到了第i个人,然后1号窗口目前的总排队时间是j,目前的最大总时间 有这个i和j的话,再预处理出前i个人的排队总时间sum[i]&a…

wpf中xps文档合并功能实现

原文:wpf中xps文档合并功能实现跟着上一篇的xps文档套打的文章,近期一直在研究xps打印技术,其中用户提到了一个需求,要求能够多页面进行打印,我的想法是,先生成xps文件,然后将文件读取出来以后,…

DCT(离散余弦变换(DiscreteCosineTransform))

离散余弦变换(Discrete Cosine Transform,简称DCT变换)是一种与傅立叶变换紧密相关的数学运算。在傅立叶级数展开式中,如果被展开的函数是实偶函数,那么其傅立叶级数中只包含余弦项,再将其离散化可导出余弦…

从源码看ConcurrentHashMap

简介 ConcurrentHashMap是线程安全的HashMap实现,这里主要研究JDK8后的ConcurrentHashMap,下面是ConcurrentHashMap的简单结构: ConcurrentHashMap基于HashMap的基本逻辑,通过CAS synchronized 来保证并发安全性。ConcurrentHas…

代码重构的方法

见:http://blog.csdn.net/u011889786/article/details/51865344 见:http://blog.csdn.net/weiky626/article/details/1602691 一.提取子函数 说白了就是一个大函数里,可以根据不同功能分成几个小函数,因为说不定,其他…

android 去掉标题栏、状态栏、横屏

// 去掉标题栏supportRequestWindowFeature(Window.FEATURE_NO_TITLE);// 全屏、隐藏状态栏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);// 横屏setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION…

Spring Boot 整合Mybatis (一)

2019独角兽企业重金招聘Python工程师标准>>> 新建spring-boot项目&#xff0c;相关依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><de…

x264 的 cache详解

在这里和下一级别的分析中有必要先讲一下这个h->mb.cache&#xff08;没法讲&#xff0c;就是cache!&#xff09;。 x264_macroblock_cache_load将参考帧中某位置的&#xff08;重建后&#xff09;数据保存进cache&#xff0c;供参考和反复使用。 x264_macroblock_cache_s…

同步/异步阻塞/非阻塞

平时开发中经常会听大家说到什么同步阻塞、异步非阻塞等等名词&#xff0c;这里我谈下自己对这两个名词的理解&#xff0c;仅仅是个人观点&#xff0c;并不一定正确。 1.阻塞/非阻塞 我认为判定阻塞还是非阻塞&#xff0c;取决于线程所做的操作是否需要将线程挂起等待。 举个…

Repeater的使用

1.页面代码 如果要分页&#xff0c;那么页面开头必须写&#xff08;<% Register Src"~/Controls/Page.ascx" TagName"Page" TagPrefix"uc1" %>&#xff09; 并且分页&#xff0c;页脚<uc1:Page ID"Page2" runat"server&…

springboot 整合 mongodb实现 批量更新数据

现需求&#xff1a;需要批量将1000个数据先查询在更新到mongodb&#xff08;如果查询不到数据&#xff0c;则添加数据&#xff09; 1&#xff1a;工具类BathUpdateOptions 1 import org.springframework.data.mongodb.core.query.Query;2 import org.springframework.data.mong…

【开题报告】基于微信小程序的校园资讯平台的设计与实现

1.选题背景与意义 随着移动互联网的快速发展&#xff0c;微信成为了人们日常生活中不可或缺的工具之一。在校园生活中&#xff0c;学生们对于校园资讯的获取和交流需求也越来越高。然而&#xff0c;传统的校园资讯发布方式存在信息不及时、传播范围有限等问题&#xff0c;无法…

三种Cache写入方式原理简介

三种Cache写入方式原理简介 在386以上档次的微机中&#xff0c;为了提高系统效率&#xff0c;普遍采用Cache&#xff08;高速缓冲存储器&#xff09;&#xff0c;现在的系统甚至可以拥有多级Cache。Cache实际上是位于CPU与DRAM主存储器之间少量超高速的静态存储器&#xff08;S…

Minor GC和Full GC

我们在日常开发中可能经常会听大家谈论GC&#xff0c;但是其实很多人对GC的种类其实并不是很了解&#xff0c;接下来我们简单介绍下Minor GC和Full GC及他们的区别。 MinorGC&#xff1a; 也可以叫作新生代GC&#xff0c;指的是发生在新生代的垃圾收集动作。因为新生代中对象大…

linux安装软件的几种方法

见&#xff1a;http://blog.csdn.net/u010509774/article/details/50593231 一、rpm包安装方式步骤&#xff1a; 1、找到相应的软件包&#xff0c;比如soft.version.rpm&#xff0c;下载到本机某个目录&#xff1b; 2、打开一个终端&#xff0c;su -成root用户&#xff1b; …