文档章节

Spring AOP源码分析(二)JDK动态代理和CGLIB介绍

乒乓狂魔
 乒乓狂魔
发布于 2015/02/07 10:22
字数 1351
阅读 736
收藏 4
本篇是介绍java实现代理对象的两种方法,JDK动态代理和CGLIB。
JDK动态代理:针对你所调用的方法是接口所定义的方法。动态的创建一个类,通过实现目标类的接口来实现代理。
CGLIB:没有限制。通过继承目标类来创建代理类,实现代理。
下面看案例:

案例一,JDK动态代理:
Person和Animals都实现了Say接口sayHello方法。现在就需要对他们的sayHello方法进行拦截。
Say接口如下:

public interface Say {

	public void sayHello();
}

Person类如下:
package com.lg.aop.base;

public class Person implements Say{
	
	private String name;
	
	public Person() {
		super();
	}

	public Person(String name) {
		super();
		this.name = name;
	}

	@Override
	public void sayHello() {
		System.out.println("My name is "+name+"!");
		throw new RuntimeException();
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	
}

Animal类如下:
public class Animals implements Say{

	@Override
	public void sayHello() {
		System.out.println("I am a animal");
	}

}

使用JDK动态代理来创建代理对象的工具类JDKDynamicProxy如下:
package com.lg.aop.base;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKDynamicProxy {
	
	public static  Object createProxy(final Object target){
		return Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler(){

			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				if(method.getName().equals("sayHello")){
					doBefore();
					try {
						method.invoke(target, args);
					} catch (Exception e) {
						doThrowing();
					}
					doAfter();
				}
				return null;
			}

		});
	}
	
	private static void doThrowing() {
		System.out.println("AOP say throw a exception");
	}
	
	private static void doBefore() {
		System.out.println("AOP before say");
	}

	private static void doAfter() {
		System.out.println("AOP after say");
	}

}

JDK动态代理就是通过Proxy.newProxyInstance来创建代理对象的:
第一个参数是ClassLoader:因为此次代理会创建一个Say接口的实现类,需要将这个类加载到jvm中,所以用到了ClassLoader。
第二个参数是代理类要实现的所有接口:当你调用这些接口的方法时都会进行拦截。
第三个参数是InvocationHandler,每次调用代理对象的方法时,都会先执行InvocationHandler的invoke方法,在该方法中实现我们的拦截逻辑。
在本案例中,InvocationHandler的invoke方法中,我们的拦截逻辑是这样的,当调用sayHello方法时,进行doBefore、doAfter、doThrowing等拦截。

测试类如下:

package com.lg.aop.base;


public class ProxyTest {

	public static void main(String[] args){
		Say say1=new Person("lg");
		say1=(Say)JDKDynamicProxy.createProxy(say1);
		say1.sayHello();
		
		System.out.println("-------------------------------");
		
		Say say2=new Animals();
		say2=(Say) JDKDynamicProxy.createProxy(say2);
		say2.sayHello();
	}
}

测试结果如下:
AOP before say
My name is lg!
AOP say throw a exception
AOP after say
-------------------------------
AOP before say
I am a animal
AOP after say

我们可以看到实现了相应的拦截。
这里说明下几个概念,
(1)Proxy.newProxyInstance的返回结果和目标对象Person实现了同样的接口,但他们之间不能相互转化。即say1=(Person)JDKDynamicProxy.createProxy(say1);是错误的。
(2)InvocationHandler的invoke(Object proxy, Method method, Object[] args)方法中的Object proxy是Proxy.newProxyInstance的返回的代理对象,不是目标对象Person,因此希望执行原目标对象的sayHello方法时,method.invoke(target, args);所传对象是原目标对象,而不是代理对象proxy。

这就是JDK动态代理的原理,目前这些拦截都是硬编码写死的,如果我们继续进一步改造,便也可以实现更加灵活的代理,有兴趣的可以实现自己的AOP。

案例二,CGLIB代理:
首先在pom文件中引入cglib库。如下:

<dependency>
		<groupId>cglib</groupId>
		<artifactId>cglib</artifactId>
		<version>3.1</version>
	</dependency>

还是针对同样的Person、Animals、Say接口。只是这次创建代理对象的方式变了,如下:
package com.lg.aop.base;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;

public class CglibProxy {

	@SuppressWarnings("unchecked")
	public static <T> T createProxy(final T t){
		Enhancer enhancer=new Enhancer();
		enhancer.setClassLoader(CglibProxy.class.getClassLoader());
		enhancer.setSuperclass(t.getClass());
		enhancer.setCallback(new InvocationHandler(){

			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				Object ret=null;
				if(method.getName().equals("sayHello")){
					doBefore();
					try {
						ret=method.invoke(t, args);
					} catch (Exception e) {
						doThrowing();
					}
					doAfter();
				}
				return ret;
			}
			
		});
		return (T)enhancer.create();
	}
	
	private static void doThrowing() {
		System.out.println("AOP say throw a exception");
	}
	
	private static void doBefore() {
		System.out.println("AOP before say");
	}

	private static void doAfter() {
		System.out.println("AOP after say");
	}
}

使用cglib的Enhancer来创建,创建出的代理对象继承了指定的class。
(1)enhancer.setClassLoader:也是指定类加载器,将创建出来的新类加载到jvm中。
(2)enhancer.setSuperclass(t.getClass()):设置目标类作为代理对象的父类。
(3)enhancer.setCallback:设置一个回调函数,每次调用代理类的方法时,先执行该回调函数。
然后就是测试:

public class ProxyTest {

	public static void main(String[] args){
		cglibTest();
	}
	
	public static void cglibTest(){
		Person p=new Person("lg");
		p=CglibProxy.createProxy(p);
		p.sayHello();
		
		System.out.println("-------------------------------");
		
		Animals animals=new Animals();
		animals=CglibProxy.createProxy(animals);
		animals.sayHello();
	}
}

p=CglibProxy.createProxy(p):由于创建出来的代理对象就是目标对象的子类,所以不用像JDK动态代理那样创建出的代理类只能是Say类型。
运行效果如下:
AOP before say
My name is lg!
AOP say throw a exception
AOP after say
-------------------------------
AOP before say
I am a animal
AOP after say

和JDK动态代理一样的效果,实现了拦截。

总结一下:
(1)当你所调用的目标对象的方法是接口所定义的方法时,可以使用JDK动态代理或者Cglib。即当你的目标类虽然实现了接口,但是所调用的方法却不是接口方法时,就无法使用JDK动态代理,因为JDK动态代理的原理就是实现和目标对象同样的接口,因此只能调用那些接口方法。
(2)Cglib则没有此限制,因为它所创建出来的代理对象就是目标类的子类,因此可以调用目标类的任何方法(除去final方法,final方法不可继承),都会进行拦截。

所以SpringAOP会优先选择JDK动态代理,当调用方法不是接口方法时,只能选择Cglib了。

© 著作权归作者所有

共有 人打赏支持
乒乓狂魔
粉丝 1016
博文 105
码字总数 271356
作品 0
长宁
程序员
私信 提问
Spring AOP 创建代理的源码解析

相关文章 Spring AOP 注解方式源码解析 Spring AOP 功能使用详解 Spring 的 getBean 方法源码解析 Spring bean 创建过程源码解 Spring 中 bean 注册的源码解析 前言 在上篇文章 Spring AOP 注...

TSMYK
01/01
0
0
Java设计模式综合运用(动态代理+Spring AOP)

AOP设计模式通常运用在日志,校验等业务场景,本文将简单介绍基于Spring的AOP代理模式的运用。 1. 代理模式 1.1 概念 代理(Proxy)是一种提供了对目标对象另外的访问方式,即通过代理对象访问...

landy8530
2018/10/05
0
0
Spring AOP 功能使用详解

相关文章 Spring 中 bean 注册的源码解析 Spring bean 创建过程源码解析 Spring 的 getBean 方法源码解析 前言 AOP 既熟悉又陌生,了解过 Spring 人的都知道 AOP 的概念,即面向切面编程,可...

TSMYK
2018/12/25
0
0
Sring如何选择JDK动态代理与CGLIB字节码增强

Spring将事务代理工厂TransactionProxyFactoryBean或自动代理拦截器BeanNameAutoProxyCreator的proxyTargetClass属性,设置为true,则使用CGLIB代理,此属性默认为false,使用JDK动态代理。 Spri...

无语年华
2018/09/11
0
0
Spring Proxy 动态代理(ProxyFactory)

一、动态代理生成技术栈分为两种: 1、JDK动态代理 JDK动态代理只能对实现了接口的类生成代理,而不能针对类 2、Cglib动态代理 CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其...

王微_1986
2018/07/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

iOS个人中心渐变动画、微信对话框、标签选择器、自定义导航栏、短信验证输入框等源码

iOS精选源码 简单的个人中心页面-自定义导航栏并予以渐变动画 程序员取悦女票的正确姿势---Tip1(iOS美容篇) iOS 前台重启应用和清除角标的问题 微信原生提醒对话框3.0 JHLikeButton - 有趣...

Android爱开源
5分钟前
0
0
Yii2使用驼峰命名的形式访问控制器

yii2在使用的时候,访问控制器的时候,如果控制器的名称是驼峰命名法,那访问的url中要改成横线的形式。例如: public function actionRoomUpdate(){//}//访问的时候就要www.test.co...

dragon_tech
8分钟前
0
0
Navicat使用教程:使用Navicat Query Analyzer优化查询性能(第2部分)

下载Navicat Monitor最新版本 Navicat Monitor 是一套安全、简单而且无代理的远程服务器监控工具。它具有强大的功能使你的监控发挥最大效用。受监控的服务器包括 MySQL、MariaDB 和 Percona ...

电池盒
15分钟前
0
0
Python3 读写utf-8文本文件

with open('testRead.txt', 'r', encoding='utf-8') as f: for each_line in f: Passwith open('testWrite.txt', 'w', encoding='utf-8') as f: f.write('写入的内容'......

编程老陆
18分钟前
0
0
Linux syslog相关函数详解

介绍 syslog是Unix系统的日志系统。可以将日志记录在本地系统中。 一个完整的syslong日志包含如下信息:程序模块 | 严重性 | 时间 | 主机名 | 进程名 | 进程ID | 正文。 syslong相关函数 1....

RongJinhui0
22分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部