Spring AOP 和 动态代理技术
AOP 是什么东西
首先来说 AOP 并不是 Spring 框架的核心技术之一,AOP 全称 Aspect Orient Programming,即面向切面的编程。其要解决的问题就是在不改变源代码的情况下,实现对逻辑功能的修改。常用的场景包括记录日志、异常处理、性能监控、安全控制(例如拦截器)等,总结起来就是,凡是想对当前功能做变更,但是又不想修改源代码的情况下,都可以考虑是否可以用 AOP 实现。
为什么要面向切面呢,我直接改源代码不是很好吗?当然没有问题,如果情况允许。但是考虑到下面这些情况,我本来写好了1000个方法,有一天,我想加入一些控制,我想在执行方法逻辑之前,检查一些系统参数,参数检查没问题再执行逻辑,否则不执行。这种情况怎么办呢,难道要修改这1000个方法吗,那简直就是灾难。还有,有些线上逻辑执行缓慢,但我又不想重新部署环境,因为那样会影响线上业务,这种情况下,也可以考虑 AOP 方式,Btrace 就是这样一个线上性能排查的神器。
Spring AOP 的用法
面向切面编程,名字好像很炫酷,但是使用方式已经被 Spring 封装的非常简单,只需要简单的配置即可实现。使用方式不是本文介绍的重点,下面仅演示最简单最基础的使用,实现对调用的方法进行耗时计算,并打印出来。
环境说明: JDK 1.8 ,Spring mvc 版本 4.3.2.RELEASE
1. 首先引用 Spring mvc 相关的 maven 包,太多了,就不列了,只列出 Spring-aop 相关的
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version> 4.3.2.RELEASE </version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
2. 在 Spring mvc 配置文件中增加关于 AOP 的配置,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true"
xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 自动扫描与装配bean -->
<context:component-scan base-package="kite.lab.spring"></context:component-scan>
<!-- 启动 @AspectJ 支持 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
</bean>
3. 创建切面类,并在 kite.lab.spring.service 包下的方法设置切面,使用 @Around 注解监控,实现执行时间的计算并输出,内容如下:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch; @Component
@Aspect
public class PerformanceMonitor { //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
@Pointcut("execution(* kite.lab.spring.service..*(..))")
public void aspect(){ } @Around("aspect()")
public Object methodTime(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 开始
Object retVal = pjp.proceed();
stopWatch.stop();
// 结束
System.out.println(String.format("方法 %s 耗时 %s ms!",pjp.getSignature().toShortString(), stopWatch.getTotalTimeMillis()));
return retVal;
}
}
4. 被切面监控的类定义如下:
package kite.lab.spring.service; public class Worker {
public String dowork(){
System.out.println("生活向来不易,我正在工作!");
return "";
}
}
5. 加载 Spring mvc 配置文件,并调用 Worker 类的方法
public static void main(String[] args) {
String filePath = "spring-servlet.xml";
ApplicationContext ac = new FileSystemXmlApplicationContext(filePath);
Worker worker = (Worker) ac.getBean("worker");
worker.dowork();
}
6. 显示结果如下:
说完用法,接下来说一下实现原理,知其然也要知其所以然。
Spring AOP 原理
AOP 的实现原理就是动态的生成代理类,代理类的执行过程为:执行我们增加的代码(例如方法日志记录)—-> 回调原方法 ——> 增加的代码逻辑。看图比较好理解:
Spring AOP 动态代理可能采用 JDK 动态代理或 CGlib 动态生成代理类两种方式中的一种, 决定用哪一种方式的判断标准就是被切面的类是否有其实现的接口,如果有对应的接口,则采用 JDK 动态代理,否则采用 CGlib 字节码生成机制动态代理方式。
代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。代理类和委托类实现相同的接口,所以调用者调用代理类和调用委托类几乎感觉不到差别。
是不是看完了定义,感觉正好可以解决切面编程方式要解决的问题。下图是基本的静态代理模式图:
而动态代理的意思是运行时动态生成代理实现类,由于 JVM 的机制,需要直接操作字节码,生成新的字节码文件,也就是 .class
文件。
JDK 动态代理
JDK 动态代理模式采用 sun 的 ProxyGenerator 的字节码框架。要说明的是,只有实现了接口的类才能使用 JDK 动态代理技术,实现起来也比较简单。
1. 只要实现 InvocationHandler
接口,并覆写 invoke
方法即可。具体实现代码如下:
package kite.lab.spring.aop.jdkaop; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* JdkProxy
*
* @author fengzheng
*/
public class JdkProxy implements InvocationHandler { private Object target; /**
* 绑定委托对象并返回一个代理类
*
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target;
//取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
} /**
* 调用方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
System.out.println("事物开始");
//执行方法
result = method.invoke(target, args);
System.out.println("事物结束");
return result;
}
}
Proxy.newProxyInstance 方法用于动态生成实际生成的代理类,三个参数依次为被代理类的类加载器、被代理类所实现的接口和当前代理拦截器。
覆写的 invoke 中可以加入我们增加的业务逻辑,然后回调原方法。
2. 被代理的类仍然用的前面 spring aop 介绍的那个worker 类,只不过我们需要让这个类实现自接口,接口定义如下:
package kite.lab.spring.service; /**
* IWorker
*
**/
public interface IWorker {
String dowork();
}
3. 实际调用如下:
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
IWorker worker = (IWorker) jdkProxy.bind(new Worker());
worker.dowork();
}
原理说明: jdkProxy.bind 会生成一个实际的代理类,这个生成过程是利用的字节码生成技术,生成的代理类实现了IWorker 接口,我们调用这个代理类的 dowork 方法的时候,实际在代理类中是调用了 JdkProxy (也就是我们实现的这个代理拦截器)的 invoke 方法,接着执行我们实现的 invoke 方法,也就执行了我们加入的逻辑,从而实现了切面编程的需求。
我们把动态生成的代理类字节码文件反编译一下,也就明白了。由于代码较长,只摘出相关部分。
首先看到类的接口和继承关系:
public final class $Proxy0 extends Proxy implements IWorker
代理类被命名为 $Proxy0 ,继承了 Proxy ,并且实现了IWorker ,这是关键点。
找到 dowork
方法,代码如下:
public final String dowork() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
super.h 就是我们实现的JdkProxy 这个类,可以看到调用了这个类的 invoke 方法,并且传入了参数 m3 ,再来看 m3 是什么
m3 = Class.forName("kite.lab.spring.service.IWorker").getMethod("dowork", new Class0);
看到了吧,m3 就是 dowork 方法,是不是流程就明确了。
但是,并不是所有的被代理的类(要被切面的类)都实现了某个接口,没有实现接口的情况下,JDK 动态代理就不行了,这时候就要用到 CGlib 字节码框架了。
CGLIB 动态代理
CGlib库使用了ASM这一个轻量但高性能的字节码操作框架来转化字节码,它可以在运行时基于一个类动态生成它的子类。厉害了吧,不管有没有接口,凡是类都可以被继承,拥有这样的特点,原则上来说,它可以对任何类进行代码拦截,从而达到切面编程的目的。
CGlib 不需要我们非常了解字节码文件(.class 文件)的格式,通过简单的 API 即可实现字节码操作。
基于这样的特点,CGlib 被广泛用于如 Spring AOP 等基于 代理模式的AOP框架中。
下面就基于 CGlib 实现一个简单的动态代理模式。
1. 创建拦截类实现 MethodInterceptor
接口,并覆intercept
方法,在此方法中加入我们增加的逻辑,代码如下:
public class MyAopWithCGlib implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("嘿,你在干嘛?");
methodProxy.invokeSuper(o, objects);
System.out.println("是的,你说的没错。");
return null;
}
2. 被代理的类依然是上面的 Worker 类,并且不需要接口。
3. 客户端调用代理方法的代码如下:
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib");
MyAopWithCGlib aop = new MyAopWithCGlib();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Worker.class);
enhancer.setCallback(aop);
Worker worker = (Worker) enhancer.create();
worker.dowork();
}
代码第一行是要将动态生成的字节码文件持久化到磁盘,方便反编译观察。
利用 CGlib 的 Enhancer 对象,设置它的继承父类,设置回调类,即上面实现的 拦截类,然后用create 方法创造一个 Worker 类,实际这个类是 Worker 类的子类,然后调用dowork方法。执行结果如下:
可以看到我们织入的代码起作用了。
4. 上面功能比较简单,它会横向切入被代理类的所有方法中,下来我们稍微做的复杂一点。控制一下,让有些方法被织入代码,有些不被织入代码,模仿 Spring aop ,我们新增一个注解,用于注解哪些方法要被横向切入。注解如下:
package kite.lab.spring.aop.AopWithCGlib; import java.lang.annotation.*; /**
* CGLIB
*
* @author fengzheng
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface CGLIB {
String value() default "";
}
5. 然后再 Worker 中增加一个方法,并应用上面的注解
package kite.lab.spring.service; import kite.lab.spring.aop.AopWithCGlib.CGLIB; /**
* Worker
*
* @author fengzheng
*/
public class Worker {
public String dowork(){
System.out.println("生活向来不易,我正在工作!");
return "";
} @CGLIB(value = "cglib")
public void dowork2(){
System.out.println("生活如此艰难,我在奔命!");
}
}
我们在 dowrok2 上应用了上面的注解
6. 在拦截方法中加入注解判断逻辑,如果加了上面的注解,就加入织入的代码逻辑,否则不加入,代码如下:
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Annotation[] annotations = method.getDeclaredAnnotations();
boolean isCglib = false;
for(Annotation annotation: annotations){
if (annotation.annotationType().getName().equals("kite.lab.spring.aop.AopWithCGlib.CGLIB")){
isCglib = true;
}
}
if(isCglib) {
System.out.println("嘿,你在干嘛?");
methodProxy.invokeSuper(o, objects);
System.out.println("是的,你说的没错。");
}
return null;
}
7. 调用方法如下:
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib");
MyAopWithCGlib aop = new MyAopWithCGlib();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Worker.class);
enhancer.setCallback(aop);
Worker worker = (Worker) enhancer.create();
worker.dowork(); worker.dowork2();
}
执行结果应该为 dowork 不执行被织入的逻辑,dowork2 执行被织入的代码逻辑,执行结果如下:
另外说一下,CGlib 不支持 final 类, CGlib 的执行速度比较快,但是创建速度比较慢,所以如果两种动态代理都适用的场景下,有大量动态代理类创建的场景下,用 JDK 动态代理模式,否则可以用 CGlib 。
标准的 Spring MVC 框架,一般都是一个服务接口类对应一个实现类,所以根据Spring AOP 的判断逻辑,应该大部分情况下都是使用的 JDK 动态代理模式。当然也可以手动改成 CGlib 模式。
古时的风筝 【微信公众号】gushidefengzheng
Spring AOP 和 动态代理技术的更多相关文章
- Java基础---Java---基础加强---类加载器、委托机制、AOP、 动态代理技术、让动态生成的类成为目标类的代理、实现Spring可配置的AOP框架
类加载器 Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader 类加载器也是Jav ...
- Spring AOP --JDK动态代理方式
我们知道Spring是通过JDK或者CGLib实现动态代理的,今天我们讨论一下JDK实现动态代理的原理. 一.简述 Spring在解析Bean的定义之后会将Bean的定义生成一个BeanDefinit ...
- Spring AOP之动态代理
软件151 李飞瑶 一.Spring 动态代理中的基本概念 1.关注点(concern) 一个关注点可以是一个特定的问题,概念.或者应用程序的兴趣点.总而言之,应用程序必须达到一个目标 ...
- Spring AOP JDK动态代理与CGLib动态代理区别
静态代理与动态代理 静态代理 代理模式 (1)代理模式是常用设计模式的一种,我们在软件设计时常用的代理一般是指静态代理,也就是在代码中显式指定的代理. (2)静态代理由 业务实现类.业务代理类 两部分 ...
- Spring AOP 前奏--动态代理
- (转)Spring AOP的底层实现技术
AOP概述 软件的编程语言最终的目的就是用更自然更灵活的方式模拟世界,从原始机器语言到过程语言再到面向对象的语言,我们看到编程语言在一步步用更自然.更强大的方式描述软件.AOP是软件开发思想的一个飞跃 ...
- Spring AOP高级——源码实现(1)动态代理技术
在正式进入Spring AOP的源码实现前,我们需要准备一定的基础也就是面向切面编程的核心——动态代理. 动态代理实际上也是一种结构型的设计模式,JDK中已经为我们准备好了这种设计模式,不过这种JDK ...
- 【Spring技术原理】Aspectj和LoadTimeWeaving的动态代理技术实现指南
前提介绍 当我们聊到Spring框架的项目实际开发中,用的强大的功能之一就是(面向切面编程)的这门AOP技术.如果使用得当,它的最大的作用就是侵入性比较少并且简化我们的工作任务(节省大量的重复性编码) ...
- Java中动态代理技术生成的类与原始类的区别 (转)
用动态代理的时候,对它新生成的类长什么样子感到好奇.有幸通过一些资料消除了心里的疑惑. 平时工作使用的Spring框架里面有一个AOP(面向切面)的机制,只知道它是把类重新生成了一遍,在切面上加上了后 ...
随机推荐
- [UWP]用Shape做动画
相对于WPF/Silverlight,UWP的动画系统可以说有大幅提高,不过本文无意深入讨论这些动画API,本文将介绍使用Shape做一些进度.等待方面的动画,除此之外也会介绍一些相关技巧. 1. 使 ...
- css简单了解
今天主要是说一下css样式表!HTML结合他使用可以是HTML页面变得很绚丽多彩! 先简单介绍一下为什么要使用CSS(Cascading Style Sheets)层叠样式表! 1.因为CSS样式表可 ...
- ssh代理上网
背景: 公司开发机没有外网,但可以通过ssh连接到另一台可以上公网的机器,所以想通过ssh代理的方式上网,简单又方便,而且需要的时候上,不需要的时候也可以不上 配置: 超级简单 在开发机上建立ssh隧 ...
- 【FFmpeg】FFmpeg常用基本命令
1.分离视频音频流 ffmpeg -i input_file -vcodec copy -an output_file_video //分离视频流 ffmpeg -i input_file -acod ...
- 设置Ubuntu下adb 及 fastboot权限
以普通用户登录linux,然后运行adb devices会提示权限不够: List of devices attached ???????????? no permissions 这是因为 ...
- php解决表单重复提交
php解决表单重复提交时间:2015-2-28 | 评论:1条评论 | 被查看了 189 次 | 标签:php, W3cui重复提交是我们开发中会常碰到的一个问题,除了我们使用js来防止表单的重复提交 ...
- 怎样用DOS命令创建txt文本文档
单击运行, 打开命令提示符. 例如在D盘创建文本文档,那么就先进入D盘,在后面写 D: 于是就进入了D盘怎样用DOS命令创建txt文本文档 然后在后面写命令 copy con 文件名.txt ,然后回 ...
- 我的IT开源之路
我开通博客这么久也从没有写过什么,那时只是喜欢看别人的技术博客,然后转发到我的私人空间有空时候读一读.这几年下来,我关注了有几百个博客.公众号.头条号.新浪微博等等,里面有无数的好文章.但是,一直也没 ...
- 文档模型(JSON)使用介绍
一.背景 E.F.Codd在1970年首次提出了数据库系统的关系模型,从此开创了数据库关系方法和关系数据理论的研究,为数据库技术奠定了理论基础,数据库技术也开始蓬勃发展.而随着几大数据库厂商陆续发布的 ...
- 一键将Web应用发布到云-Azure Web App
我们现在越来越多的传统应用,逐步向云端迁移,原先私有云的部署模式,逐步向云端PaaS IaaS转变.例如: 我们在云端Azure中申请VM虚拟机,将我们的Web应用部署到VM的IIS中,同时做云服务的 ...