Java动态代理

代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

举一个例子:老师要批作业,可以找学习委员代收,学一委员整理好之后,再送到老师的办公室。这个场景中,学习委员就是一个代理。

##为什么要用代理模式

  • 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
  • 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

静态代理

静态代理,就是采用代理模式思想,通过硬编码定义一个代理类,来实现代理的功能。由于静态代理灵活性较弱,在实际应用中,大多数采用动态代理模式。

动态代理模式

jdk动态代理

Jdk动态代理是基于接口的。

  • Step1 定义一个interface Hello
1
2
3
public interface Hello {
void sayHello(String str);
}
  • Step2 定义HelloImpl
1
2
3
4
5
6
7
8
9
10
11
12
public final class HelloImpl implements Hello {
private int[] array;

public HelloImpl() {
this.array = new int[1024*10];
}

@Override
public void sayHello(String str) {
System.out.println("hello " + str);
}
}
  • Step3 实现InvocationHandler的invoke方法
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
public class LogInvocationHandler implements InvocationHandler {
private Hello hello;

public LogInvocationHandler(Hello hello) {
this.hello = hello;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("sayHello".equals(method.getName())) {
System.out.println("You said: " + Arrays.toString(args));
}
return method.invoke(hello, args);
}


public static void main(String[] args) {
Object proxy = Proxy.newProxyInstance(LogInvocationHandler.class.getClassLoader(), // 1. 类加载器
new Class<?>[]{Hello.class}, // 2. 代理需要实现的接口,可以有多个
new LogInvocationHandler(new HelloImpl()));// 3. 方法调用的实际处理者

Hello hello = (Hello) proxy;
hello.sayHello("world");
System.out.println(hello);
}
}

但是Jdk动态代理使用上,有局限性。不能所有的类,都要实现一个interface,这会大大增加代码的冗余。如果使用继承的话,就能够解决这个问题。

CGLIB

cglib通过继承来实现动态代理,cglib的底层实现,是基于节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。如下是class文件的格式。

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
ClassFile { 
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}

magic: 作为一个魔数,确定这个文件是否是一个能被虚拟机接受的class文件,值固定为0xCAFEBABE
minor_versionmajor_version:分别表示class文件的副,主版本号,不同版本的虚拟机实现支持的Class文件版本号不同。
constant_pool_count:常量池计数器,constant_pool_count的值等于常量池表中的成员数加1。
constant_pool:常量池,constant_pool是一种表结构,包含class文件结构及其子结构中引用的所有字符常量、类或接口名、字段名和其他常量。
access_flagsaccess_flags是一种访问标志,表示这个类或者接口的访问权限及属性,包括有ACC_PUBLICACC_FINALACC_SUPER等等。
this_class:类索引,指向常量池表中项的一个索引。
super_class:父类索引,这个值必须为0或者是对常量池中项的一个有效索引值,如果为0,表示这个class只能是Object类,只有它是唯一没有父类的类。
interfaces_count:接口计算器,表示当前类或者接口的直接父接口数量。
interfaces[]:接口表,里面的每个成员的值必须是一个对常量池表中项的一个有效索引值。
fields_count:字段计算器,表示当前class文件中fields表的成员个数,每个成员都是一个field_info
fields:字段表,每个成员都是一个完整的fields_info结构,表示当前类或接口中某个字段的完整描述,不包括父类或父接口的部分。
methods_count:方法计数器,表示当前class文件methos表的成员个数。
methods:方法表,每个成员都是一个完整的method_info结构,可以表示类或接口中定义的所有方法,包括实例方法,类方法,以及类或接口初始化方法。
attributes_count:属性表,其中是每一个attribute_info,包含以下这些属性,InnerClassesEnclosingMethodSyntheticSignatureAnnonation等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 动态代理不能是final类
* 如果是final类,会抛出异常:java.lang.IllegalArgumentException: Cannot subclass final class com.wsy.learn.jvm.autoproxy.cglib.HelloConcrete
*/
public class HelloConcrete {
/**
* 注意,此处如果定义的是final方法,则不会进入intercept方法进行方法增强
* @param str
* @return
*/
public final String sayHello(String str) {
return "HelloConcrete: " + str;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("You said: " + Arrays.toString(args));
return proxy.invokeSuper(obj, args);
}

public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());

HelloConcrete hello = (HelloConcrete)enhancer.create();
System.out.println(hello.sayHello("I love you!"));
}
}

动态代理最佳实践 - SpringAOP

关于什么是SpringAOP,在此不继续阐述。

springAOP既采用了jdk proxy,也采用了cglib。
spring会根据情况,来选择jdk动态代理还是cglib动态代理。

  • 如果要代理的对象实现了接口,则默认采用jdk proxy。
  • 如果要代理的对象实现了接口,也可以强制使用cglib。需要配置<aop:aspectj-autoproxy proxy-target-class=”true”/>
  • 如果要代理的对象没有实现接口,则采用cglib动态代理。
  • 但是注意,敲黑板!敲黑板!敲黑板!
    • 采用cglib的前提下,final类不能进行动态代理会抛出异常java.lang.IllegalArgumentException: Cannot subclass final class xxx(final类不能被继承,所以你懂得)。
    • 采用cglib的前提下,final方法也不能进行动态代理,不过不会抛出异常,而是直接跳过功能增强。
    • 采用jdk proxy,final类和final方法都能正常工作。
# java

评论