Java的三种代理模式
今天主要介绍Java中的三种代理模式。
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
上面是百度百科的定义。
代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象功能的扩展。
其分为静态代理模式和动态代理模式。
我们先来看静态代理。
静态代理
先定义一个水果接口
package com.mec.test;
public interface IFruits {
void doSomething();
}
再写一个实现水果接口了的苹果类(目标对象)
package com.mec.test;
public class Apple implements IFruits {
@Override
public void doSomething() {
System.out.println("我爱吃苹果!");
}
}
代理对象和目标对象实现相同的接口 ,代理对象是橘子类。
package com.mec.test;
public class Orange implements IFruits {
private IFruits fruit;
public Orange(IFruits fruit) {
this.fruit = fruit;
}
@Override
public void doSomething() {
System.out.println("前置拦截:我喜欢吃一种水果!");
fruit.doSomething();
System.out.println("后置拦截:橘子也爱!!!");
}
}
Test类:把目标对象设置进橘子类中
package com.mec.test;
public class Test {
public static void main(String[] args) {
Apple apple = new Apple();
Orange orange = new Orange(apple);
orange.doSomething();
}
}
我们看看输出结果:

可以看到,我们通过代理机制想要做的就是增加前置和后置拦截功能。对目标对象的doSomething()方法进行功能的扩展。
我们构造出来的这个代理对象,实际上与目标对象都实现了同一个接口,在要实现的接口方法中调用目标对象的doSomething()方法,并在原方法前和后,加上自己想要做的操作,这样就可以实现在不改变原代码的基础上(因为原代码也不可改)增加自己想实现的代码。
例如,觉得原代码判断条件不够,可以再多加一些判断条件,如果不满足,咱就不执行原代码了;后面可以判断结果是否满足条件等等。这就是前后置拦截的作用。
而这些操作在用户调用代理对象时,与调用原目标对象的方式是相同的,都调用了doSomething()方法,最终执行的代码也是相同的(都执行的是Apple的doSomething()方法),只是多加了前后置拦截。对目标对象的doSomething()方法进行了功能的扩展。
但是!
代理对象必须要提前写出,如果接口层发生了变化,代理对象的代码也要进行更改。而这仅仅是构造一个类的代理对象而已,就这么麻烦,这不现实啊!
所以,我们来看看动态代理,它解决了我们之前的问题。由于java底层封装了实现细节,所以代码非常简单。
JDK代理–Proxy代理模式
这是编写JDK的大佬程序员们为我们写好的工具,我们可以直接用。
我把它做成了一个小工具,为了方便自己使用。这样以后就不用再写重复的代码了。
这次跟上面差不多,稍微复杂一些,先写一个接口。
package com.mec.proxy.some;
public interface ISomething {
public String doSomething(String str);
}
再写一个目标对象,该类实现ISomething接口。
package com.mec.proxy.some;
public class TargetClass implements ISomething {
public TargetClass() {
}
@Override
public String doSomething(String str) {
System.out.println("正在执行doSomething方法!");
System.out.println("[ " + str + " ]");
return str;
}
public String dealSomething(String str) {
System.out.println("正在执行dealSomething方法!");
System.out.println("{ " + str + " }");
return str;
}
}
如上述代码所示,TargetClass 类中有两个方法,一个是重写接口中的抽象方法doSomething(String str),一个是我们自己写的dealSomething(String str)方法。这样做的目的是为了测试代理对象到底能调用目标对象的那些方法。
下面是JDK代理工具类:
package com.mec.proxy.jdk.core;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxy {
public JDKProxy() {
}
@SuppressWarnings("unchecked")
public static <T> T getProxy(T target) {
Class<?> klass = target.getClass();
return (T) Proxy.newProxyInstance(
klass.getClassLoader(),
klass.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置拦截!");
Object object = method.invoke(target, args);
System.out.println("后置拦截!");
return object;
}
});
}
}
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h ),
就是我们这里写的Proxy.newProxyInstance方法。
这个方法就是构造代理对象的方法,它最后会返回一个生成的代理对象。
接收的三个参数为:
ClassLoader loader:指定当前目标对象使用类加载器,写法是固定的,即与代码中写法相同
Class<?>[] interfaces:目标对象实现的接口的类型,写法固定
InvocationHandler h:事件处理接口,需传入一个实现类,一般直接使用匿名内部类,也就是直接new。
我上面把生成的代理对象直接返回了。所以外界可以去调用 得到的代理对象的方法(不是全部方法,后面测试总结)。
执行该方法实质上调用的是public Object invoke(Object proxy, Method method, Object[] args) 方法。这个方法原本没有代码,是我们自己写的!这里我们也加了前后置拦截。
方法执行过程举例:
我们执行哪个方法,该方法的method等参数就会被取到,作为public Object invoke(Object proxy, Method method, Object[] args) 方法的形参传入,并执行我们所写的带有前后置拦截的代码,当然重点是我们也通过反射机制执行了该method方法!
测试Test:
package com.mec.proxy.jdk.test;
import com.mec.proxy.jdk.core.JDKProxy;
import com.mec.proxy.some.ISomething;
import com.mec.proxy.some.TargetClass;
public class JDKTest {
public JDKTest() {
}
public static void main(String[] args) {
TargetClass target = new TargetClass();
ISomething something = JDKProxy.getProxy(target);
something.doSomething("1111");
System.out.println("-----------------------------");
System.out.println(something instanceof TargetClass);//false
System.out.println(something instanceof ISomething);
}
}
测试结果:

上图所示,我们得到的代理对象可以点出doSomething方法,但是不能点出dealSomething方法,所以可以得到结论。

Proxy代理模式:
1、被代理的类,必须有接口;
2、产生的代理对象,其类型是接口类型的派生类类型;
3、代理对象只能调用接口方法。
可以看出静态代理和JDK代理有一个共同的特点,就是目标对象必须实现一个或多个接口,如果类没有实现接口,则可以使用CGlib代理模式。
CGLib代理
前提:需要引入cglib的jar文件,导包。
同样,我也写成了一个工具
package com.mec.proxy.cglib.core;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGLibProxy {
public CGLibProxy() {
}
public static <T> T getProxy(T target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy arg3) throws Throwable {
System.out.println("前置拦截!");
Object object = method.invoke(target, args);
System.out.println("后置拦截!");
return object;
}
});
@SuppressWarnings("unchecked")
T targetProxy = (T) enhancer.create();
return targetProxy;
}
}
这个getProxy方法同样会返回一个生成的代理对象。
测试类Test:
package com.mec.proxy.cglib.test;
import java.lang.reflect.Method;
import com.mec.proxy.cglib.core.CGLibProxy;
import com.mec.proxy.some.ISomething;
import com.mec.proxy.some.TargetClass;
public class CGLibTest {
public CGLibTest() {
}
public static void main(String[] args) {
TargetClass target = new TargetClass();
TargetClass targetProxy = CGLibProxy.getProxy(target);
targetProxy.doSomething("12345");
System.out.println("...................................");
targetProxy.dealSomething("12345");
System.out.println("...................................");
System.out.println(targetProxy instanceof TargetClass);//true
System.out.println(targetProxy instanceof ISomething);
}
}
测试结果:

可以看到两个方法都被执行了,而且加了前后置拦截。
总结:
CGLib代理模式:
1、被代理的类,不必须实现接口;
2、由于CGLib代理的原理是,创建一个被代理类的子类对象,因此, 如果被代理的类存在不能被继承的方法,则,这个代理类对象当然就无法调用!即,被代理类中的final方法是不能被代理的;当然,若被代理类本身是final类,则,不能被代理!
3、代理对象可以调用除了final / static修饰的其它所有方法。