JAVA新手必须要会的01~动态代理


打算写一个Java新手入门必须要会的系列,这个系列是入门之后,必须要学会理解的东西,对于以后的Java开发会特别有帮助,第一个选定的题材就是动态代理,理解了动态代理对于理解Spring AOP和mybatis,有特别大的帮助。

动态代理可以做什么?

可以对代码进行增强,例如给方法增加事务、日志、统计信息等(Spring AOP),mybatis框架也是用到了动态代理。

简单说下什么是动态代理

在编程中,类A有个方法buyTicket(),这个方法就是买票,我们要调用买票方法,常规是先new A(),然后就直接调用就行了,动态代理是通过某种方法,动态生成了一个类,这个类可以帮助我们调用买票的方法,当我们执行A.buyTicket()时候,实际是动态代理类调用买票的接口并返回结果。看起来就好像我们自己执行了buyTicket()一样。程序员是感觉不到代理类的存在的。

如果你懂代理服务器的概念,可能会更容易理解代理这个概念。

所以重点就是有2个:
1、某种方法,动态生成了一个类,是什么方法?
2、为什么我们感觉不到代理这个行为?

静态代理

先说个静态代理,静态代理的意思是我们写一个ProxyA类(动态代理是自动生成的代理类),代替我们执行buyTicket()方法:

1
2
3
4
5
6
7
8
class ProxyA {
A a;
public int buyTicket() {
System.out.println("开始执行买票方法");
a.buyTicket();
System.out.println("结束执行买票方法");
}
}

这样做的坏处就是每次代理一个类,虽然没改A类,但是都需要手工增加1个类,开发人员很苦恼,增加了很多的重复工作量。每次需要买票的时候,都是明确的调用了ProxyA的方法,可以很明显感受到代理类的存在。

为什么感觉不到代理的行为?

代理类帮我们做了事情,但是为什么程序员没有感觉到这个行为呢?或者说是怎么做到没有侵入的呢?一般来说,要给方法A加日志,必须要修改方法A的源代码,为啥动态代理就可以做到不改A的源代码,还可以在调用A的时候加日志呢?

答案是在调用方法buyTicket的前后增加了日志或者事务代码。
但是这也解释不了,为什么我们感受不到代理行为呢?因为静态代理,用的是组合,动态代理,用的是接口和继承。动态代理类要知道目标类的方法,有2种方式,一种是继承A类,一种是A类是个接口,代理类实现这个接口,就可以知道接口有哪些方法了。

代理类继承或者实现了目标类

这样在框架里,例如Spring,在生成目标类的时候,实际上生成的是代理类,调用目标类的所有的方法,都是调用代理类来实现的,这个动作在框架里做好了,所以程序员感受不到被代理了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//源代码
@Autowired
private A a;

public void buy() {

a.buyTicket();
}

// 实际上框架执行的代码,此时一定要理解,生成了1个代理类,只不过这个类ProxyA
// 要么是A的子类,要么是实现a的接口的类。
private ProxyA a;

public void buy() {

a.buyTicket();
}

某种方法是什么方法?

对于Spring来说:
如果被代理的类是一个接口,那么就会使用JDK的动态代理。
如果被代理的类不是接口,就只能使用cglib来生成代理类,cglib底层使用的是asm框架来动态
生成了字节码。

对于mybatis来说:
应该就是JDK的动态代理。

来一点代码?

往上关于代理的代码有很多,关键点是java.lang.reflect.InvocationHandlerjava.lang.reflect.Proxy这2个类,具体的代码我就不贴了,可以自己去搜索Java动态代理,有很多的例子。

需要注意的一些知识

由于动态代理需要接口或者继承,所以如果你用spring框架,那么在使用AOP的时候,需要注意以下几点:

  • 由于final类不能被继承,所以final类不能被代理。
  • 如果方法是final的方法,代理失败。
  • 如果方法是static的,代理失败。
  • 私有的private方法也不能被代理,所以你要想spring的事务生效,必须是public方法;这里需要注意的是,同1个类里有A、B2个方法,B方法加了事务的注解,A方法调用B方法,事务不会生效,可以思考下为什么?
  • cglib生成的代理类是目标类的子类,会重写目标类的所有方法。

对于第2条和第3条,分情况讨论,如果是接口方式,由于接口中的方法不能是final和static,所以不能被代理;

如果是普通的cglib,使用继承重写机制来实现代理,那么final和static方法,都不能被重写,自然也就不能被代理了。