静态代理
代理模式也是一种很常见的设计模式。它使用代理对象完成用户请求,屏蔽用户对真实对象的访问,就如同现实中的代理一样,代理人被授权执行当事人的一些事宜,而无需当事人出面,从第三方角度来看,似乎当事人并不存在,因为它只和代理人通信。
在软件设计上,使用代理模式的意图很多,比如因为安全原因,需要屏蔽客户端直接访问真实对象,或在远程调用中,需要代理类处理远程方法的调用技术细节,也可能为了提高系统性能,对真实对象进行封装,从而达到延迟加载的目的。
代理模式可以用于多种场合,如用于远程调用的网络代理、考虑安全因素的安全代理和延迟加载的代理模式。
延迟加载的核心思想是:如果当前没有使用这个组件,则不需要真正初始化它,使用一个代理对象替代它的原有位置,只要在真正需要使用的时候,才对它进行加载。使用代理模式的延迟加载是非常有意义的,首先它可以在时间轴上分散系统压力,尤其是在系统启动时,不必完成所有的初始化工作,从而加速启动时间,其次,对于很多真实的主题而言,在软件启动直到被关闭的整个过程,可能根本不会被调用,初始化这些数据无疑是一种真正的浪费。
在系统启动时,将消耗资源最多的方法都使用代理模式分离,就可以加快系统的启动速度,减少用户等待时间。而在用户真正做查询时,再由代理类单独去加载真实的数据库查询类,完成用户请求。
下面用获取数据库连接来示例:
创建一个接口:
interface IDBQuery {
String request();
}
之后真实操作类和代理类都实现这个接口:
真实操作类:
public class DBQuery implements IDBQuery{
public DBQuery() {
System.out.println("我是真实类--构造方法");
try {
//可能包含数据库链接等耗时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public String request() {
System.out.println("我是真正的实现方法--");
return "request String";
}
}
代理类:
public class DBQueryProxy implements IDBQuery{
DBQuery db=null;
@Override
public String request() {
System.out.println("我是代理类的代理方法--");
if(db==null) {
db=new DBQuery();
}
return db.request();
}
}
模仿客户端访问:
public class Demo{
public static void main(String[] args) {
DBQueryProxy dbP=new DBQueryProxy();
System.out.println("我是返回的结果--"+dbP.request());
}
}
结果:
我是代理类的代理方法--
我是真实类--构造方法
我是真正的实现方法--
我是返回的结果--request String
将代理模式用于实现延迟加载,可以有效提升系统的启动速度,对改善用户体验有很大的帮助。
动态代理
动态代理是指在运行时,动态生成代理类。即,代理类的字节码将在运行时生成并载入当前的ClassLoader,与静态代理类相比,动态类有诸多好处。首先不需要为真实的主题写一个形式完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也是非常烦人的事,如果接口有变动,则真实的主题和代理类都要修改,不利于系统维护,其次,使用一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。
动态代理采用字节码动态生成加载技术,在运行时生成并加载类。
生成动态代理类的方法有很多,如JDK自带的动态代理、CGLIB、javassist、或者ASM库,JDK的动态代理使用非常简单,它内置在JDK中,因此不需要引入第三方jar包,但相对功能比较弱。CGLIB和javassist都是高级的字节码生成库,总体性能比JDK自带的动态代理好,而且功能十分强大。ASM是低级的字节码生成工具,使用ASM已经近乎于在使用java bytecode编程,对开发人员的要求最高,当然也是性能最好的一种动态代理生成工具。但ASM的使用实在过于频繁,而且性能也没有数量级的提升,与CGLIB等高级字节码生成工具相比,ASM可维护性也较差,如果不是对性能有苛刻要求的场合,还是推荐CGLIB和javassist。
以DBQuery为例进行展示动态代理的实现。
1.使用JDK的动态代理生成代理对象:
JDK的动态代理需要实现一个处理方法调用的Handler,用于实现代理方法的内部逻辑。
依然创建一个接口:
interface IDBQuery {
String request();
}
创建真正的实现类:
public class DBQuery implements IDBQuery{
public DBQuery() {
System.out.println("我是真实类--构造方法");
try {
//可能包含数据库链接等耗时操作
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public String request() {
System.out.println("我是真正的实现方法--");
return "request String";
}
}
创建jdk代理类:
public class JdkDBQueryProxy implements InvocationHandler{
IDBQuery real=null;
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(real==null) {
real=new DBQuery();
}
return real.request();
}
public static IDBQuery createJdkProxy(){
IDBQuery jdkProxy=(IDBQuery) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[] {IDBQuery.class},
new JdkDBQueryProxy());
return jdkProxy;
}
}
模仿客户端调用:
public class Demo{
public static void main(String[] args) {
IDBQuery idbQuery=null;
idbQuery=JdkDBQueryProxy.createJdkProxy();
idbQuery.request();
}
}
结果:
我是真实类--构造方法
我是真正的实现方法--
生成了一个实现IDBQuery接口的代理类,代理类的内部逻辑由JdkDBQueryProxy决定,生成代理类之后由
newProxyInstance()方法返回该代理类的一个实例,至此,一个完整的JDK动态代理就完成了。
CGLIB和javassist的动态代理的使用和JDK的动态代理使用非常相似,CGLIB的动态代理也需要实现一个处理代理逻辑的切入类:
public class CgLibDBQueryProxy implements MethodInterceptor{
IDBQuery real=null;
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
if(real==null) {
real=new DBQuery();
}
return real.request();
}
public static IDBQuery createCglibProxy() {
Enhancer enhancer=new Enhancer();
//指定切入器,定义代理类逻辑
enhancer.setCallback(new CgLibDBQueryProxy());
//指定实现的接口
enhancer.setInterfaces(new Class[] {IDBQuery.class});
//生成代理类的实例
IDBQuery cglibProxy=(IDBQuery) enhancer.create();
return cglibProxy;
}
}
使用javassist生成动态代理可以使用两种方式,一种是使用代理工厂创建,另一种是使用动态代码创建,使用代理工厂创建时,方法与CGLIB相似,也需要用于实现一个代理逻辑的处理的Handler:
public class JavassistDBQueryProxy implements MethodHandler{
IDBQuery real=null;
@Override
public Object invoke(Object arg0, Method arg1,
Method arg2, Object[] arg3) throws Throwable {
// TODO Auto-generated method stub
if(real==null) {
real=new DBQuery();
}
return real.request();
}
public static IDBQuery createJavassistProxy() throws Exception {
ProxyFactory factory=new ProxyFactory();
//指定接口
factory.setInterfaces(new Class[] {IDBQuery.class});
Class proxyClass=factory.createClass();
IDBQuery javassistProxy=(IDBQuery) proxyClass.newInstance();
((ProxyObject)javassistProxy).setHandler(new JavassistDBQueryProxy());
return javassistProxy;
}
}
四种代理的高频率调用的耗时结果:
JDK动态代理创建最快,这是因为在这个内置实现中defineClass()方法被定义为native实现,故性能高于其他几种实现。
但在代理类的函数调用性能上JDK的动态代理就不如GCLIB和javassist的基于动态代码的代理,而Javassist的基于代理工厂的代理实现性能最差,甚至不如JDK的实现。在实际开发应用中,代理类的方法调用频率通常要远远高于代理类的实际生成频率,相同类的重复生成会使用cache,故动态代理对象的方法调用性能应该作为性能的主要关注点。
就动态代理的方法调用性能而言,CGLIB和javassist基于动态代码代理都优于JDk自带的动态代理。此外JDK的动态代理要求代理类和真实主题都显现同一个接口,而CGLIB和javassist没有强制要求。
Hibernate框架就是采用了代理模式进行延迟加载,有兴趣可以看一下hibernate关于属性延迟加载的内容,大概就是把用户自定义的实体类变成了相应的子类代理类,之后对属性进行延迟加载,get时才会加载相关数据。