代理模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司。
代理模式的实现
代理模式可以有两种实现的方式,一种是静态代理类,另一种是各大框架都喜欢的动态代理。下面我们主要讲解一下这两种代理模式。
静态代理
委托类与代理类实现同一个接口,代理类有一个构造方法入参为委托类,这样我们就可以通过代理类来新增一些功能例如打印日志等等。
//接口
public interface AppService {
String createApp(String name);
void deleteApp(String name);
}
/**
* 具体实现类
*/
public class AppServiceImpl implements AppService {
@Override
public String createApp(String name) {
System.out.println("["+name+"] created.");
return name;
}
@Override
public void deleteApp(String name) {
System.out.println("["+name+"] delete.");
}
}
/**
* 代理类--代理实现类
*/
public class AppServiceProxy implements AppService {
//目标对象
private AppService appService;
//通过构造方法传入目标对象
public AppServiceProxy(AppService appService){
this.appService = appService;
}
@Override
public String createApp(String name) {
//在代理对象前我们可以添加一些自己的操作
System.out.println("before operation");
String result = appService.createApp(name);
//在代理对象后我们也可以添加一些自己的操作
System.out.println("after operation");
return name;
}
@Override
public void deleteApp(String name) {
appService.deleteApp(name);
}
}
//客户端运行
public class StaticClient {
public static void main(String[] args) {
AppService target = new AppServiceImpl();
AppServiceProxy proxy = new AppServiceProxy(target);
proxy.createApp("static proxy");
}
}
运行结果:
before operation
1----App[static proxy] has been created.
after operation
静态代理类优缺点
优点:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,new AppServiceImpl()可以应用工厂将它隐藏,如上只是举个例子而已。
缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为AppServiceProxy类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。
举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,增加都可以添加上打印日志的功能)
即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。
动态代理
(可以先看看这个回答:Java 动态代理作用是什么? - 知乎 )
动态代理有别于静态代理,是根据代理的对象,动态创建代理类。这样,就可以避免静态代理中代理类接口过多的问题。动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy通过固定的规则生成。
动态代理,包括JDK动态代理和CGLIB实现的代理,下面案例为JDK动态代理。
java动态代理:
利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。cglib动态代理:
利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。JDK动态代理和CGLIB字节码生成的区别?
JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 因为是继承,所以该类或方法不要声明成final。适用场景
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
如何强制使用CGLIB实现AOP
1、添加CGLIB库,SPRING_HOME/cglib/*.jar
2、在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>效率比较
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。
其步骤如下:
- 编写一个委托类的接口,即静态代理的(Subject接口)
- 实现一个真正的委托类,即静态代理的(RealSubject类)
- 创建一个自定义实现类,实现InvocationHandler接口,并重写该invoke方法
- 在测试类中,生成动态代理的对象(通过Proxy.newProxyInstance())。
- 通过自定义实现类InvocationHandler来连接委托类和代理类,这里的代理类不同于静态代理中(静态代理中自定义的实现类同时也是代理类),动态代理的代理类其实是由Proxy.newProxyInstance()生成的。
- 代理Class的构造器创建对象时,需要传入InvocationHandler。通过构造器传入一个引用,由代理对象的内部一个成员变量invocationHandler接收,并且代理对象的每个方法内部都会调用handler.invoke()。
- InvocationHandler对象成了代理对象和目标对象的桥梁,不像静态代理这么直接。
第一二步骤,和静态代理一样,不过说了。第三步,代码如下:
//创建一个自定义实现类,实现InvocationHandler,重写invoke()
public class LoggerHandler implements InvocationHandler {
//这个就是我们要代理的真实对象
private Object target;
//构造方法,给我们要代理的真实对象赋初值
public LoggerHandler(Object target){
this.target = target;
}
//Object proxy:被代理的对象
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
System.out.println("Entered "+target.getClass().getName()+"-"+method.getName()
+",with arguments{"+args[0]+"}");
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("before invoke");
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object result = method.invoke(target, args);
//调用目标对象的方法 (调用厂家的方法(createApp)及参数(Kevin Test))
System.out.println("Before return:"+result);
//在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after invoke");
return result;
}
}
//客户端运行
public class Client {
public static void main(String[] args) {
//我们要代理的真实对象
AppService target = new AppServiceImpl();
// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler invocationHandler = new LoggerHandler(target);
/**
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
AppService proxy =
(AppService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
invocationHandler);
System.out.println(proxy.getClass().getName());
// for(Method m:proxy.getClass().getDeclaredMethods()){
// System.out.println(m.getName());
//
// }
proxy.createApp("proxy Test1");
proxy.deleteApp("proxy Test2");
}
}
运行结果:
com.sun.proxy.$Proxy0
Entered com.boss.design.structuralModel.Proxy.AppServiceImpl-createApp,with arguments{proxy Test1}
before invoke
1----App[proxy Test1] has been created.
Before return:proxy Test1
after invoke
Entered com.boss.design.structuralModel.Proxy.AppServiceImpl-deleteApp,with arguments{proxy Test2}
before invoke
2------App[proxy Test2] has been delete.
Before return:null
after invoke
可以看到,我们可以通过LoggerHandler代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。
动态代理优点:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强。