面试官:说说你对Spring AOP的实现机制的理解!

栏目: Java · 发布时间: 5年前

内容简介:AOP(Aspect Orient Programming),一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能。本文主要介绍Spring AOP的两种代理实现机制,JDK动态代理和CGLIB动态代理。

面试官:说说你对Spring AOP的实现机制的理解!

AOP(Aspect Orient Programming),一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能。

本文主要介绍Spring AOP的两种代理实现机制,JDK动态代理和CGLIB动态代理。

静态代理是编译阶段生成AOP代理类,也就是说生成的字节码就织入了增强后的AOP对象;动态代理则不会修改字节码,而是在内存中临时生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的,诸如private的方法也是不可以作为切面的。

我们分别通过实例来研究AOP的具体实现。

直接使用Spring AOP

首先定义需要切入的接口和实现。为了简单起见,定义一个Speakable接口和一个具体的实现类,只有两个方法sayHi()和sayBye()。

public interface Speakable {  
 void sayHi();  
 void sayBye();  
}  
@Service  
public class PersonSpring implements Speakable {  
 @Override  
 public void sayHi() {  
 try {  
 Thread.currentThread().sleep(30);  
 } catch (Exception e) {  
 throw new RuntimeException(e);  
 }  
 System.out.println("Hi!!");  
 }  
 @Override  
 public void sayBye() {  
 try {  
 Thread.currentThread().sleep(10);  
 } catch (Exception e) {  
 throw new RuntimeException(e);  
 }  
 System.out.println("Bye!!");  
 }  
} 

接下来我们希望实现一个记录sayHi()和sayBye()执行时间的功能。

定义一个MethodMonitor类用来记录Method执行时间

public class MethodMonitor {  
 private long start;  
 private String method;  
 public MethodMonitor(String method) {  
 this.method = method;  
 System.out.println("begin monitor..");  
 this.start = System.currentTimeMillis();  
 }  
 public void log() {  
 long elapsedTime = System.currentTimeMillis() - start;  
 System.out.println("end monitor..");  
 System.out.println("Method: " + method + ", execution time: " + elapsedTime + " milliseconds.");  
 }  
} 

光有这个类还是不够的,希望有个静态方法用起来更顺手,像这样

MonitorSession.begin();  
doWork();  
MonitorSession.end(); 

说干就干,定义一个MonitorSession

public class MonitorSession {  
 private static ThreadLocal<MethodMonitor> monitorThreadLocal = new ThreadLocal<>();  
 public static void begin(String method) {  
 MethodMonitor logger = new MethodMonitor(method);  
 monitorThreadLocal.set(logger);  
 }  
 public static void end() {  
 MethodMonitor logger = monitorThreadLocal.get();  
 logger.log();  
 }  
} 

万事具备,接下来只需要我们做好切面的编码,

@Aspect  
@Component  
public class MonitorAdvice {  
 @Pointcut("execution (* com.deanwangpro.aop.service.Speakable.*(..))")  
 public void pointcut() {  
 }  
 @Around("pointcut()")  
 public void around(ProceedingJoinPoint pjp) throws Throwable {  
 MonitorSession.begin(pjp.getSignature().getName());  
 pjp.proceed();  
 MonitorSession.end();  
 }  
} 

如何使用?我用了spring boot,写一个启动函数吧。

@SpringBootApplication  
public class Application {  
 @Autowired  
 private Speakable personSpring;  
 public static void main(String[] args) {  
 SpringApplication.run(Application.class, args);  
 }  
 @Bean  
 public CommandLineRunner commandLineRunner(ApplicationContext ctx) {  
 return args -> {  
 // spring aop  
 System.out.println("******** spring aop ******** ");  
 personSpring.sayHi();  
 personSpring.sayBye();  
 System.exit(0);  
 };  
 }  
} 

运行后输出:

jdk dynamic proxy begin monitor..Hi!!end monitor..Method: sayHi, execution time: 32 milliseconds.begin monitor..Bye!!end monitor..Method: sayBye, execution time: 22 milliseconds. 

JDK动态代理

刚刚的例子其实内部实现机制就是JDK动态代理,因为Person实现了一个接口。

为了不和第一个例子冲突,我们再定义一个Person来实现Speakable, 这个实现是不带Spring Annotation的,所以他不会被Spring托管。

public class PersonImpl implements Speakable {  
 @Override  
 public void sayHi() {  
 try {  
 Thread.currentThread().sleep(30);  
 } catch (Exception e) {  
 throw new RuntimeException(e);  
 }  
 System.out.println("Hi!!");  
 }  
 @Override  
 public void sayBye() {  
 try {  
 Thread.currentThread().sleep(10);  
 } catch (Exception e) {  
 throw new RuntimeException(e);  
 }  
 System.out.println("Bye!!");  
 }  
} 

重头戏来了,我们需要利用InvocationHandler实现一个代理,让它去包含Person这个对象。那么再运行期实际上是执行这个代理的方法,然后代理再去执行真正的方法。所以我们得以在执行真正方法的前后做一些手脚。JDK动态代理是利用反射实现,直接看代码。

public class DynamicProxy implements InvocationHandler {  
 private Object target;  
 public DynamicProxy(Object object) {  
 this.target = object;  
 }  
 @Override  
 public Object invoke(Object arg0, Method arg1, Object[] arg2)  
 throws Throwable {  
 MonitorSession.begin(arg1.getName());  
 Object obj = arg1.invoke(target, arg2);  
 MonitorSession.end();  
 return obj;  
 }  
 @SuppressWarnings("unchecked")  
 public <T> T getProxy() {  
 return (T) Proxy.newProxyInstance(  
 target.getClass().getClassLoader(),  
 target.getClass().getInterfaces(),  
 this  
 );  
 }  
} 

通过getProxy可以得到这个代理对象,invoke就是具体的执行方法,可以看到我们在执行每个真正的方法前后都加了Monitor。

我实现了一个工厂类来获取Person代理对象

public class PersonProxyFactory {  
 public static Speakable newJdkProxy() {  
 // 代理PersonImpl  
 DynamicProxy dynamicProxy = new DynamicProxy(new PersonImpl());  
 Speakable proxy = dynamicProxy.getProxy();  
 return proxy;  
 }  
} 

具体使用

// jdk dynamic proxy  
System.out.println("******** jdk dynamic proxy ******** ");  
Speakable jdkProxy = PersonProxyFactory.newJdkProxy();  
jdkProxy.sayHi();  
jdkProxy.sayBye(); 

输出结果:

******** jdk dynamic proxy ********   
begin monitor..  
Hi!!  
end monitor..  
Method: sayHi, execution time: 32 milliseconds.  
begin monitor..  
Bye!!  
end monitor..  
Method: sayBye, execution time: 22 milliseconds. 

CGLib动态代理

我们再新建一个Person来,这次不实现任何接口。

public class Person {  
 public void sayHi() {  
 try {  
 Thread.currentThread().sleep(30);  
 } catch (Exception e) {  
 throw new RuntimeException(e);  
 }  
 System.out.println("Hi!!");  
 }  
 public void sayBye() {  
 try {  
 Thread.currentThread().sleep(10);  
 } catch (Exception e) {  
 throw new RuntimeException(e);  
 }  
 System.out.println("Bye!!");  
 }  
} 

如果Spring识别到所代理的类没有实现Interface,那么就会使用CGLib来创建动态代理,原理实际上成为所代理类的子类。

public class CGLibProxy implements MethodInterceptor {  
 private static CGLibProxy instance = new CGLibProxy();  
 private CGLibProxy() {  
 }  
 public static CGLibProxy getInstance() {  
 return instance;  
 }  
 private Enhancer enhancer = new Enhancer();  
 @SuppressWarnings("unchecked")  
 public <T> T getProxy(Class<T> clazz) {  
 enhancer.setSuperclass(clazz);  
 enhancer.setCallback(this);  
 return (T) enhancer.create();  
 }  
 @Override  
 public Object intercept(Object arg0, Method arg1, Object[] arg2,  
 MethodProxy arg3) throws Throwable {  
 MonitorSession.begin(arg1.getName());  
 Object obj = arg3.invokeSuper(arg0, arg2);  
 MonitorSession.end();  
 return obj;  
 }  
} 

类似的通过getProxy可以得到这个代理对象,intercept就是具体的执行方法,可以看到我们在执行每个真正的方法前后都加了Monitor。

在工厂类中增加获得Person代理类的方法,

public static Person newCglibProxy() {  
 CGLibProxy cglibProxy = CGLibProxy.getInstance();  
 Person proxy = cglibProxy.getProxy(Person.class);  
 return proxy;  
} 

具体使用

// cglib dynamic proxy  
System.out.println("******** cglib proxy ******** ");  
Person cglibProxy = PersonProxyFactory.newCglibProxy();  
cglibProxy.sayHi();  
cglibProxy.sayBye(); 

输出结果:

begin monitor..Hi!!end monitor..Method: sayHi, execution time: 53 milliseconds.begin monitor..Bye!!end monitor..Method: sayBye, execution time: 14 milliseconds. 

小结

对比JDK动态代理和CGLib代理,在实际使用中发现CGLib在创建代理对象时所花费的时间却比JDK动态代理要长,实测数据

Method: newJdkProxy, execution time: 5 milliseconds.  
Method: newCglibProxy, execution time: 18 milliseconds. 

所以CGLib更适合代理不需要频繁实例化的类。

在具体方法执行效率方面,理应是不通过反射的CGlib更快一些,然后测试结果并非如此,还需要高手指教。

JDKMethod: sayHi, execution time: 32 milliseconds.CGLibMethod: sayHi, execution time: 53 milliseconds.  

【责任编辑:庞桂玉 TEL:(010)68476606】


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms for Image Processing and Computer Vision

Algorithms for Image Processing and Computer Vision

Parker, J. R. / 2010-12 / 687.00元

A cookbook of algorithms for common image processing applications Thanks to advances in computer hardware and software, algorithms have been developed that support sophisticated image processing with......一起来看看 《Algorithms for Image Processing and Computer Vision》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具