本文从四个方面认识动态代理
- 什么是代理?
- 为什么使用代理?
- 如何使用动态代理?
- 动态代理的原理
一 什么是代理
要理解动态代理是什么先要了解一种设计模式那就是代理模式。
- 代理模式的定义
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
一个是真正的你要访问的对象(目标类或委任类),一个是代理对象,真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。
- 各个组成部分
抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
- 不同的代理模式
代理模式分为静态代理、动态代理。
静态代理
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。动态代理
动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
当然本文的主要讨论是动态代理。
二 为什么使用代理?
之所以使用代理是因为代理有以下几个有点:
职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
高扩展性
三 如何使用动态代理?
- 静态代理
再了解使用动态代理之前,先了解静态代理的用法这样才能对之后的动态代理有更深的认识。
下面从一个简单的需求例子说明:
假设有一个叫Exec的类里面有n个方法m1 m2 m3 ...
class Exec{
public int m1(){
一顿操作...
}
public int m2(){
又一顿操作...
}
public int m3(){
又又一顿操作...
}
...
}
现有一个需求:在每个方法执行前后打印日志。以下有几个方案
方案1直接修改
最直观的方法,代码如下:
class Exec{
public int m1(){
System.out.println("m1开始...");
一顿操作...
System.out.println("m1结束...");
return result;
}
public int m2(){
System.out.println("m2开始...");
又一顿操作...
System.out.println("m2结束...");
return result;
}
public int m3(){
System.out.println("m3开始...");
又又一顿操作...
System.out.println("m3结束...");
return result;
}
... ...
}
上面的方案是有问题的:
- 直接修改源程序,不符合对扩展开放,对修改关闭的开闭原则。
- 如果Exec类有几十个、上百个方法,修改量太大。
- 存在重复代码(都是在核心代码前后打印日志)。
- 日志打印硬编码在代理类中,不利于后期维护:比如你花一段时间写完,被告知此功能取消,你又要去删除日志打印代码。
方案2使用静态代理类
根据上文对静态代理的描述,该方案如下:
- 将Exec抽取为接口
- 创建目标类ExecImpl实现Exec
- 创建代理类ExecProxy实现Exec
接口:
/**
* Exec接口
*/
public interface Exec{
int m1();
int m2();
int m3();
...
}
目标对象实现类:
/**
* 目标对象实现类,实现Exec接口
*/
public class ExecImpl implements Exec{
@Override
public int m1( ) {
一顿操作...
return 1;
}
@Override
public int m2( ) {
又一顿操作...
return 2;
}
@Override
public int m3( ) {
又又一顿操作...
return 3;
}
... ...
}
代理对象实现类:
/**
* 代理对象实现类,实现Exec接口
*/
public class ExecProxy implements Exec{
//代理对象内部维护一个目标对象引用
private Exec target;
//构造方法,传入目标对象
public ExecProxy(Exec target) {
this.target = target;
}
//调用目标对象的m1,并在前后打印日志
@Override
public int m1( ) {
System.out.println("m1方法开始...");
一顿操作...
System.out.println("m1方法结束...");
return result;
}
@Override
public int m2( ) {
System.out.println("m2方法开始...");
又一顿操作...
System.out.println("m2方法结束...");
return result;
}
@Override
public int m3( ) {
System.out.println("m3方法开始...");
又又一顿操作...
System.out.println("m3方法结束...");
return result;
}
... ...
}
使用代理对象完成目标对象里的方法,并且打印日志
public class Test {
public static void main(String[] args) {
//把目标对象通过构造器传入代理对象
Exec exec= new ExecProxy(new ExecImpl());
//代理对象调用目标对象的方法,并在前后打印日志
exec.m1();
exec.m2();
exec.m3();
...
}
}
静态代理的优点:可以在不修改目标对象的前提下,对目标对象进行功能的扩展和拦截。但是它也仅仅解决了上一种方案4大缺点中的第1点:直接修改源程序,不符合开闭原则。应该对扩展开放,对修改关闭。
- 静态代理的问题
上面案例中,代理类是我们事先编写的,而且要和目标对象类实现相同接口。由于ExecImpl(目标对象)需要日志功能,我们即编写了ExecProxy(代理对象),并通过构造器传入ExecImpl(目标对象),调用目标对象同名方法的同时添加增强代码。
但是这里有个问题。代理对象构造器的参数类型是Exec,它只能接受Exec的实现类对象,亦即我们写的代理类ExecProxy只能给Exec做代理,它们绑定死了。
如果现在我们系统需要全面改造,给其他类也添加日志打印功能,就得为其他几百个接口都各自写一份代理类。
自己手动写一个类并实现接口实在太麻烦。如何少写或者不写代理类,却能完成代理功能?仔细一想,我们其实想要的并不是代理类,而是代理对象。那么,能否让JVM根据接口自动生成代理对象呢?
比如,有没有一个方法,我传入接口,它就给我自动返回代理对象?
答案是有就是接下来讲得动态代理。
方案3动态代理
- 方式1
public class ProxyTest {
public static void main(String[] args) throws Throwable {
ExecImpl target = new ExecImpl();
//传入目标对象
//目的:1.根据它实现的接口生成代理对象 2.代理对象调用目标对象方法
Exec execProxy = (Exec) getProxy(target);//目标对象(委托对象)
execProxy .m1();
execProxy .m2();
}
private static Object getProxy(final Object target) throws Exception {
//参数1:随便找个类加载器给它, 参数2:目标对象实现的接口,让代理对象实现相同接口
Class proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
Object proxy = constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法开始执行...");
Object result = method.invoke(target, args);
System.out.println("返回结果是:" + result);
System.out.println(method.getName() + "方法执行结束...");
return result;
}
});
return proxy;
}
}
打印结果:
m1方法开始执行...
返回结果是: 1
m1方法执行结束...
m2方法开始执行...
返回结果是: 2
m2方法执行结束...
可惜,还是太麻烦了。有没有更简单的方式获取代理对象?
public class ProxyTest {
public static void main(String[] args) throws Throwable {
ExecImpl target = new ExecImpl();//委托对象(目标对象)
Exec execProxy = (Exec) getProxy(target);
execProxy.m1();
execProxy.m2();
}
private static Object getProxy(final Object target) throws Exception {
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),//类加载器
target.getClass().getInterfaces(),//让代理对象和目标对象实现相同接口
new InvocationHandler(){
//代理对象的方法最终都会被JVM导向它的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法开始执行...");
Object result = method.invoke(target, args);
System.out.println("返回结果是:" + result);
System.out.println(method.getName() + "方法执行结束...");
return result;
}
}
);
return proxy;
}
}
打印结果:
m1方法开始执行...
返回结果是: 1
m1方法执行结束...
m2方法开始执行...
返回结果是: 2
m2方法执行结束...
四 动态代理的原理
Proxy类的静态方法getProxyClass()方法,给它传一个接口Class对象,它能返回一个加强版Class对象。
Proxy类和JVM,让我们不写代理类却直接得到代理Class对象,进而得到代理对象。
静态代理
动态代理