本篇是介绍java实现代理对象的两种方法,JDK动态代理和CGLIB。
JDK动态代理:针对你所调用的方法是接口所定义的方法。动态的创建一个类,通过实现目标类的接口来实现代理。
CGLIB:没有限制。通过继承目标类来创建代理类,实现代理。
下面看案例:
案例一,JDK动态代理:
Person和Animals都实现了Say接口sayHello方法。现在就需要对他们的sayHello方法进行拦截。
Say接口如下:
Person类如下:
Animal类如下:
使用JDK动态代理来创建代理对象的工具类JDKDynamicProxy如下:
JDK动态代理就是通过Proxy.newProxyInstance来创建代理对象的:
第一个参数是ClassLoader:因为此次代理会创建一个Say接口的实现类,需要将这个类加载到jvm中,所以用到了ClassLoader。
第二个参数是代理类要实现的所有接口:当你调用这些接口的方法时都会进行拦截。
第三个参数是InvocationHandler,每次调用代理对象的方法时,都会先执行InvocationHandler的invoke方法,在该方法中实现我们的拦截逻辑。
在本案例中,InvocationHandler的invoke方法中,我们的拦截逻辑是这样的,当调用sayHello方法时,进行doBefore、doAfter、doThrowing等拦截。
测试类如下:
测试结果如下:
我们可以看到实现了相应的拦截。
这里说明下几个概念,
(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库。如下:
还是针对同样的Person、Animals、Say接口。只是这次创建代理对象的方式变了,如下:
使用cglib的Enhancer来创建,创建出的代理对象继承了指定的class。
(1)enhancer.setClassLoader:也是指定类加载器,将创建出来的新类加载到jvm中。
(2)enhancer.setSuperclass(t.getClass()):设置目标类作为代理对象的父类。
(3)enhancer.setCallback:设置一个回调函数,每次调用代理类的方法时,先执行该回调函数。
然后就是测试:
p=CglibProxy.createProxy(p):由于创建出来的代理对象就是目标对象的子类,所以不用像JDK动态代理那样创建出的代理类只能是Say类型。
运行效果如下:
和JDK动态代理一样的效果,实现了拦截。
总结一下:
(1)当你所调用的目标对象的方法是接口所定义的方法时,可以使用JDK动态代理或者Cglib。即当你的目标类虽然实现了接口,但是所调用的方法却不是接口方法时,就无法使用JDK动态代理,因为JDK动态代理的原理就是实现和目标对象同样的接口,因此只能调用那些接口方法。
(2)Cglib则没有此限制,因为它所创建出来的代理对象就是目标类的子类,因此可以调用目标类的任何方法(除去final方法,final方法不可继承),都会进行拦截。
所以SpringAOP会优先选择JDK动态代理,当调用方法不是接口方法时,只能选择Cglib了。
JDK动态代理:针对你所调用的方法是接口所定义的方法。动态的创建一个类,通过实现目标类的接口来实现代理。
CGLIB:没有限制。通过继承目标类来创建代理类,实现代理。
下面看案例:
案例一,JDK动态代理:
Person和Animals都实现了Say接口sayHello方法。现在就需要对他们的sayHello方法进行拦截。
Say接口如下:
1
2
3
4
|
public
interface
Say {
public
void
sayHello();
}
|
Person类如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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类如下:
1
2
3
4
5
6
7
8
|
public
class
Animals
implements
Say{
@Override
public
void
sayHello() {
System.out.println(
"I am a animal"
);
}
}
|
使用JDK动态代理来创建代理对象的工具类JDKDynamicProxy如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
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等拦截。
测试类如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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();
}
}
|
测试结果如下:
1
2
3
4
5
6
7
8
|
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库。如下:
1
2
3
4
5
|
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>
3.1
</version>
</dependency>
|
还是针对同样的Person、Animals、Say接口。只是这次创建代理对象的方式变了,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
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:设置一个回调函数,每次调用代理类的方法时,先执行该回调函数。
然后就是测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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类型。
运行效果如下:
1
2
3
4
5
6
7
8
|
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了。