AOP的实现方式比较,cglib vs jdk

 2023-09-05 阅读 202 评论 0

摘要:为什么80%的码农都做不了架构师?>>> #问题描述 今天继续看aop的实践原理,当使用AOP获取一个bean的实例时报错: Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy2 cannot be cast to com.chak.test.M

为什么80%的码农都做不了架构师?>>>   hot3.png

#问题描述 今天继续看aop的实践原理,当使用AOP获取一个bean的实例时报错:

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy2 cannot be cast to com.chak.test.MyLife

其中MyLife的定义为:

public class MyLife implements IHabit {public void goToWord(){System.out.println(this.getClass().getName() + " is going to work...");}public void backHome(){System.out.println(this.getClass().getName() + "has back to home...");}@Overridepublic void doingSomething() {System.out.println(this.getClass().getName() + " is doing some big work!");}
}

包含工作的两件大事:上班和回家,并实现了一个抽象的“爱好”接口(接口就一个doingSomething() 方法)。

然后在.xml配置文件中这样配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"default-lazy-init="false"><bean id="myAdvice" class="com.chak.test.MyAdvice"/><bean id="myLife" class="com.chak.test.MyLife"/><aop:config><aop:pointcut id="toWorkPoint" expression="execution(* com.chak.test.MyLife.goToWord())"/><aop:pointcut id="backHomePoint" expression="execution(* com.chak.test.MyLife.backHome())"/><aop:pointcut id="doingSomething" expression="execution(* com.chak.test.IHabit.doingSomething())"/><aop:aspect ref="myAdvice"><aop:before method="sayBye" pointcut-ref="toWorkPoint"/><aop:after method="sayWelcome" pointcut-ref="backHomePoint"/><aop:after method="encourage" pointcut-ref="doingSomething"/></aop:aspect></aop:config></beans>

切入类定义的两个方法toWorkPoint和backHomePoint,以及接口定义的doingSomething。但是当项目启动起来之后,

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String [] args){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("test.xml");//直接将实现了接口的类转变成对象是会报错滴MyLife myLife = (MyLife) applicationContext.getBean("myLife");//但是下面的转化却是ok的,因为它没有转成对象而是转成接口对象IHabit iHabit = (IHabit) applicationContext.getBean("myLife");iHabit.doingSomething();}
}

就出现了文章开头说到的那个ClassCastException。我们知道通过AOP切面织入通知的bean,在获取他们的实例时得到的并不是一个普通的bean对象,而是封装过的代理对象。从上面这个例子可以看出:

当通过AOP处理一个继承了若干接口的类后,它所实例化的bean代理对象并不能直接强制转换成原类的实例对象。

当通过AOP处理一个继承了若干接口的类后,它所实例化的bean代理对象可以转换成其实现的接口的实例对象。

总结就是当一个类继承了若干接口后,AOP处理生成的代理对象会有差异。这也是我们下面要探索的地方。

#JDK提供的动态代理(java.lang.reflect包) 默认情况下,Spring AOP底层使用的代理技术基于JDK支持的代理框架,这个框架主要包括java.lang.reflect 包中的Proxy类和InvocationHandler 接口。下面先来看看他们是怎样联合起来实现动态代理效果的。

先看代码——

/*
*BookFacede接口
*/
public interface BookFacade {void addBook();
}/*
*BookFacede接口实现类
*/
public class BookFacadeImpl {public void addBook() {System.out.println("add book...");}
}/**
*BookFacedeImpl实现类
*/
public class BookFacadeImpl implements BookFacade{@Overridepublic void addBook() {System.out.println("add book...");}
}/**
*代理类
*对应的Spring中的基于JDK代理类是:org.springframework.aop.framework.JdkDynamicAopProxy
*/
public class BookFacadeProxy implements InvocationHandler {private Object target;public Object bind(Object target) {this.target = target;return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);  //只能绑定接口(这是一个缺陷,cglib弥补了这一缺陷)}@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {//AOP也正是通过这个入口来织入新通知的System.out.println("begin action...");Object result = method.invoke(target, args);System.out.println("end action...");return result;}}public class Main {public static void main(String [] args){BookFacadeProxy bookFacadeProxy = new BookFacadeProxy();//BookFacadeImpl bookFacade = (BookFacadeImpl) bookFacadeProxy.bind(new BookFacadeImpl());//直接转换成实现类是会报错的//BookFacade bookFacade = (BookFacade) bookFacadeProxy.bind(new BookFacadeImpl());//转成接口类对象是OK的bookFacade.addBook();}
}

这也正是为什么最早使用Spring AOP编程时会爆出上面的Exception的原因。因为JDK的代理对象的创建是基于接口的,核心方法Proxy.newProxyInstace()的定义如下:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();//基于接口final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class. 注意这里获取相应的类时,用的是intfs接口*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}
}

可见,JDK代理方式实现的代理对象是:

  • 基于接口的,所以被代理对象必须实现接口
  • 不能直接转成实现接口的类的实例对象(有点绕口...)
  • JDK代理方式比较适合用于基于接口的编程(废话)

#CGLib代理 所以,如果我们要使用代理,同时还想让封装后的代理对象可以直接转型成被代理对象直接使用,就要使用更强大的CGLib代理。

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;/*CGLib代理类*/
//Spring中对应的是CglibAopProxy
public class CGLibProxy implements MethodInterceptor {private Object targetObject;// CGLib需要代理的目标对象public Object createProxyObject(Object obj) {this.targetObject = obj;Enhancer enhancer = new Enhancer();enhancer.setSuperclass(obj.getClass());enhancer.setCallback(this);Object proxyObj = enhancer.create();return proxyObj;}public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {Object obj;System.out.println("Before intercept...");obj = method.invoke(targetObject, args);System.out.println("After intercept...");return obj;}
}

然后就可以用了:

public static void main(String [] args){BookFacadeImpl bookFacade = (BookFacadeImpl)new CGLibProxy().createProxyObject(new BookFacadeImpl());//注意这里通过代理对象是一个类的实例,而非接口bookFacade.addBook();
}

那么我们就要问了,CGLib代理为什么能够创建普通类的代理对象呢?因为:

CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

这也正是Enhancer对象做的,收集被代理类和代理类的信息,通过字节码技术重新构建一个新的子类,用新的子类来创建一个对象。Cool~

CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少(当然,因为这是基于字节码的技术啊),但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。

同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

#Spring AOP对CGLib和JDK的选择 Spring支持两种代理方式,默认情况下,

  • 如果被代理对象是一个没有实现任何接口的对象,Spring会使用CGLib代理(因为只有它能支持);
  • 如果被代理对象是一个实现了接口的对象,Spring会使用JDK代理; 如果需要强制使用CGLib代理,需要在aop:config中添加proxy-target-class="false"属性。

以上

转载于:https://my.oschina.net/djzhu/blog/850433

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/813.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息