代理模式是23种设计模式中非常经典的一种模式,在日常生活中到处充满了代理模式的痕迹,常见的比如火车代售点买票,各种公共服务大厅,以及各种网上购物平台其实都可以看成是代理模式的缩影,或者再形象点各种浏览器都可以看成是我们上网的代理角色。
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
(1).职责清晰 真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
(3).高扩展性
(1)最经典的就是Spring AOP的实现,通过代理实现了日志记录,事务,方法耗时等非核心业务本职责的功能的统一控制。
(2)在Hibernate类似相关的框架中,仅仅定义接口就可以通过代理动态实现CURD相关操作。
(3)其他各种字节码操纵的场景。
在Java里面总体上有三种代理实现:
例子如下: 定义一个接口
public interface Animal {
public String run();
}
定义一个实现类:
public class Dog implements Animal {
@Override
public String run() {
return " 小狗跑..... ";
}
}
定义一个代理类,同样实现该接口:
public class ProxyRole implements Animal {
private Animal proxy;
public ProxyRole(Animal proxy) {
this.proxy = proxy;
}
@Override
public String run() {
System.out.println(" before execute...... ");
String result=proxy.run();
System.out.println(result);
System.out.println(" after execute...... ");
return result;
}
}
如何使用:
public class TestMain {
public static void main(String[] args) {
//真实角色.
Animal dog=new Dog();
//代理角色.
ProxyRole proxyRole=new ProxyRole(dog);
//代替执行.
proxyRole.run();
}
}
JDK动态接口的代理的特点是,其代理角色可以在运行时动态生成而不需要在编译的时候提前定义,正是因为这个特点,所以扩展性和灵活性更强,其原理是在运行时会动态生成一个类,该类实现了所定义的接口,然后在我们把我们还需要定义一个InvocationHandler的实现类,在这个类里面重写invoke方法,并附加自己与业务无关的逻辑,比如计数或者方法耗时,日志等等,最终通过反射调用真实角色的业务方法。
例子如下:
public class CountTimeProxyInvocation implements InvocationHandler {
private Object target;
public CountTimeProxyInvocation(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println(proxy+" "+method+" "+ args.toString());
System.out.println("name:"+proxy.getClass().getName());
System.out.println(method+" "+Arrays.toString(args));//传入的参数类型
long start=System.nanoTime();
System.out.println("调用之前.......");
// Object result=ms.get(method.getName()).invoke(target,args);
Object result=method.invoke(target,args);
long cost=System.nanoTime()-start;
System.out.println("调用之后,"+method.getName()+" 耗时: "+cost+" ns");
return result;
}
}
上面这个实现了InvocationHandler的类,实现了方法耗时的统计。
private static void testMap(){
Map map= (Map) Proxy.newProxyInstance(TestCountTimeProxy.class.getClassLoader(),
new Class[]{Map.class},//必须接口类型,可以有多个接口
new CountTimeProxyInvocation(new HashMap())
);
map.put("t",12);
map.get("1");
map.size();
}
然后,我们就能感受它的强大之处,只要是有接口声明的类,都可以采用这个方法来统计耗时,比如上面的Map例子,正这里如果是List或者是Set同样适用。
JDK动态代理要求每一个类必须要有接口定义才可以实现动态代理功能,所以有时候我们可能仅仅是为了一个普通类代理,这样以来就必须绑定接口了,而cglib框架可以实现普通类的代理,对于类里面不包含final修饰的方法都可以实现代理,例子如下:
定义一个普通的类:
public class HelloConcrete {
public String sayHello(String str) {
return "HelloConcrete: " + str;
}
}
实现代理访问:
// CGLIB动态代理
// 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法.
class MyMethodInterceptor implements MethodInterceptor{
...
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
logger.info("You said: " + Arrays.toString(args));
return proxy.invokeSuper(obj, args);
}
}
// 2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象.
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!"));
cglib并不是jdk自带的方案,它需要引入maven的依赖才能使用:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.8</version>
</dependency>
代理模式的技术核心有三点:
(1)动态字节码生成
(2)动态字节码类的装载
(3)反射调用真实角色的方法
理解了上面的内容学习代理模式就非常容易,先从JDK动态代理说起,前面说到JDK动态代理的会在运行时生成接口的实现类,并且该实现类会继承Proxy类,所以也注定了JDK动态代理只能代理接口不能代理类,因为Java不支持多继承,在使用的时候我们执行方法调用会从代理角色经过,然后由代理角色调用我们实现的Invocation的invoke方法,在invoke方法里面我们可以自定义额外的功能,最后通过反射调用真实角色方法,完成整个调用链。
而cglib代理的原理与JDK动态代理类似,不同之处在于cglib代理在运行时候,动态的生成了一个继承了真实的角色的代理类,然后实现了cglib的Factory接口,在运行的时候,同样通过代理类转发请求调用,先调用我们设置的callback里面回调的Interceptor方法,这样我们就可以在这个方法里面控制与核心业务无关的逻辑,最后通过反射调用真实角色的方法完成整个调用链。cglib代理功能更强大,其底层集成了ASM直接操作字节码的类库,并且可以控制仅仅对需要代理的方法执行代理,至于其他的方法可以忽略,这样以来可以拥有更好的性能。
本文主要介绍了代理模式的应用,并结合实际的例子详细的描述了Java里面三种代理模式的原理和实现,代理模式是一项非常有用的技术,通过代理角色转发请求,可以实现非常灵活的扩展功能。