我们都知道cglib代理是基于继承来实现的 Customer.java
public class Customer { public void findLove() { System.out.println("肤白貌美大长腿"); } }CglibMeipo.java
public class CglibMeipo implements MethodInterceptor { public Object getInstance(Class<?> clazz) throws Exception { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallbacks(new Callback[]{this}); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before 我是媒婆,我要给你找对象,现在已经确认了你的要求,你觉得怎样"); Object obj = methodProxy.invokeSuper(o, objects); System.out.println("end 如果你觉得好,那就办事了"); return obj; } }测试方法
@Test public void test1() throws Exception{ System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/quyixiao/project/spring_tiny/src/main/java/com/spring_101_200/test_111_120/test_111_cglib_proxy"); Customer obj = (Customer) new CglibMeipo().getInstance(Customer.class); obj.findLove(); }【输出结果】 before 我是媒婆,我要给你找对象,现在已经确认了你的要求,你觉得怎样 肤白貌美大长腿 end 如果你觉得好,那就办事了
同样像jdk动态代理一样,实现了方法加切面的功能 具体怎样实现的,目前没有深入研究,有兴趣的小伙伴可以看看这一块的生成cglib源码看看
通过上面的代码,普通的cglib代理的使用方法,那么Spring中是如何使用cglib代理的呢? 我们来看下这一段代码 我将lookup-method 标签实现这一块cglib考备过来,实现如下 SpringCglib.class
public class SpringCglib { private static final Class<?>[] CALLBACK_TYPES = new Class<?>[] {NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class}; public Object getInstance(Class<?> clazz,int index) throws Exception { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallbacks(new Callback[]{NoOp.INSTANCE,new LookupOverrideMethodInterceptor(),new ReplaceOverrideMethodInterceptor()}); enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(index)); enhancer.setCallbackTypes(CALLBACK_TYPES); return enhancer.create(); } class MethodOverrideCallbackFilter implements CallbackFilter { private int a; public MethodOverrideCallbackFilter(int a) { this.a = a; } @Override public int accept(Method method) { return a; } } class LookupOverrideMethodInterceptor implements MethodInterceptor { public LookupOverrideMethodInterceptor() { } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { System.out.println("======lookup=====start==========="); Object xxxx = mp.invokeSuper(obj, args); System.out.println("======lookup=====end======="); return null; } } class ReplaceOverrideMethodInterceptor implements MethodInterceptor { public ReplaceOverrideMethodInterceptor() { } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { System.out.println("======replace=====start==="); Object xxxx = mp.invokeSuper(obj, args); System.out.println("======replace=====end====="); return null; } } }测试方法如下:
@Test public void test2() throws Exception{ Customer obj = (Customer) new SpringCglib().getInstance(Customer.class,2); obj.findLove(); }【输出结果】 replace=start= 肤白貌美大长腿 replace=end===
我们可以看到,当index值传入2时,调用的是CALLBACK_TYPES Class 数组中的第三个类ReplaceOverrideMethodInterceptor.class 的intercept方法, 而上图的红圈标识的和普通cglib实现不一样,Spring通过这种cglib的实现方式,从而来实现,根据不同的index来控制,是调用lookup-method的实现,还是replace-method的实现,而解析lookup-method标签和replace-method标签的解析方法如下:
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 仅当前的Spring默认的bean子元素下且为 lookup-method时有效 if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { Element ele = (Element) node; // 获取要修饰的方法 String methodName = ele.getAttribute(NAME_ATTRIBUTE); // 获取要配置的bean String beanRef = ele.getAttribute(BEAN_ELEMENT); LookupOverride override = new LookupOverride(methodName, beanRef); override.setSource(extractSource(ele)); overrides.addOverride(override); } } } public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 仅当在Spring默认的bean的子元素下且为<replace-method时有效 if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) { Element replacedMethodEle = (Element) node; // 提取要替换的旧的方法 String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); // 提取对应的新的替换方法 String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); ReplaceOverride replaceOverride = new ReplaceOverride(name, callback); // Look for arg-type match elements. List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT); for (Element argTypeEle : argTypeEles) { // 记录参数 String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE); match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle)); if (StringUtils.hasText(match)) { replaceOverride.addTypeIdentifier(match); } } replaceOverride.setSource(extractSource(replacedMethodEle)); overrides.addOverride(replaceOverride); } } }无论是ReplaceOverride ,还是LookupOverride 都继承MethodOverride,如下图 在解析结束后都被加入到了MethodOverrides的overrides属性中 ,所以Spring 可以根据不同的类型调用不同的cglib实现, cglib的实现不需要通过实现接口来实现,感觉是万能的代理 ,但是也有一些不足: 1)无法通知(advise) Final 方法,因为他们不能被覆写 2)你需要将 CGLIB 二进制发行包入在 classpath 下面 那么写好了cblib代理和jdk动态代理 ,我们来总结一下,两点的区别吧
JDK 动态代理 :其代理对象必需是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理
CGLIB 代理:实现原理类似于 JDK 动态代理的原理,只是它在运行期间生成的代理对象是针对目标类扩展子类,CGLIB 的高效代码生成包,底层是依靠 ASM(开源的 java 字节码编辑类库)操作字节码实现的,性能上比 JDK 要强
感觉Spring的cglib实现,比我们普通用法感觉高端一些。
可能我的认知还在不断完善中,如果后继在cglib源码这一块有突破的话,再来完善吧 如果想看lookup-method的解析,可以看我另一篇博客。
本文的github地址 https://github.com/quyixiao/spring_tiny/blob/master/src/main/java/com/spring_101_200/test_111_120/test_111_cglib_proxy