什么是代理?
代理是什么?简单举个例子,你想和某某明星说话,但是某明星肯定不会直接和你说话(除非你很有身份,当我没说),这时候你就得去找他的助理交流,然后助手再和这个明星交流。对于明星来说,助手就是代理,你们其他人沟通都不要全部来找我了,全去找我的助手吧。
在代码中也差不多是这样一个场景,直接交流就相当于直接调用目标类的方法(非代理),而代理就是新创建一个类(我们叫他代理类),由这个类去调用目标类的方法,然后其他类就不去直接调用目标类的方法了,调用方式改成其他类去告诉代理类,让代理类来调用目标类方法。
我这里就用一个故事来说明:A、B和C是朋友,平时都有交流。但是某一天,B生A的气了,这时候B就不和A说话了,于是B告诉A说:要想和我说什么叫我做什么,你去找C说,C听完再来告诉我。于是C就成为B的收话员(代理人)了,什么事情以后都由C来叫B做。C收到A的消息再告诉B去做事,这就是代理。
当然A和B能对话,B也可以找C来做自己的代理人,这都是没问题的
非代理:
开始A可以直接和B交流(直接调用B的方法),如下:
A类代码:
public class A {
private B b = new B();
public void let_B_do(String something){
b.doSomething(something);
}
public static void main(String[] args) {
A a = new A();
a.let_B_do("Sweep");
}
}
B类代码:
public class B {
public void doSomething(String something){
System.out.println("B -> " + something);
}
}
A直接叫(调用)B去做事:B -> Sweep
不使用代理就是A类直接去调用B类的方法。
使用代理:
B生气了,A就无法和B交流了(A无法直接调用B类的方法了),于是A就去找C说(调用C的方法),叫C告诉B(C调用B)去做某事,使用了C类就相当于使用了代理
A类代码:
public class A {
private C c = new C();
public void let_C_info_B_do(String something){
c.info_B_do(something);
}
public static void main(String[] args) {
A a = new A();
a.let_C_info_B_do("Sweep");
}
}
B类代码:
public class B {
public void doSomething(String something){
System.out.println("B -> " + something);
}
}
C类代码:
public class C {
private B b = new B();
public void info_B_do(String something){
System.out.println("找B去做事");
b.doSomething(something);
}
}
这样一个过程就是代理,这里找C办事的A就是委托人,也就是委托类,C就是代理人,也就是代理类,代理B的事务
注意:C只是代理,它可以告诉B去做事,当然也可以刷小手段,从中添油加醋,这就要看你在info_B_do()这个方法里面怎么写。
代理的用处:
就像我上面所说的,C收到消息可以选择如实告诉A,当然也可以添油加醋,补充功能。在现实中的项目,比如我们有一个登录的功能,但是没有做验证,我们可以对用户类的登录方法做代理,在登录的时候,做一些验证啥的(当然我们也可以修改登录的方法,但是修改会造成很多问题。这就涉及到一些原则,设计模式提倡开闭原则:对扩展开放,对修改关闭。因为直接修改会导致出现更多的问题,维护起来可能会更加麻烦,而且有的时候这份代码可能是别人写的,其他很多地方可能依赖了你修改的地方,修改可能会导致程序崩溃。而且代理类可以有选择的使用,不需要代理还可以选择原来的类。)
总结成一句话就是,可以插入需要的操作(功能添加),这是AOP的体现。(面向切面编程,切开添加一些东西进去)
代理的几种实现:
- 静态代理
- 动态代理(JDK代理)
- CGLib代理
静态代理:
首先就是静态代理,就和我们上面例子比较类似,但是有一点不同,不同就在之前我们C调用B的do()
方法用的是info_B_do()
,而静态代理用的是相同的方法名去调用的,也就是info_B_do()
改成do()
,这个怎么理解呢?C可以去叫B做事,C类相当于可以完成B做的事情,那么就可以当作C做了这件事。静态代理的代理类的代理方法是提前写好的,是固定不变的,只可以代理固定的几个方法,新添加的方法要去代理,就得添加代理类的方法
要实现静态代理要满足:继承相同的接口或者父类(这样委托类就和代理类的方法名一样了,其次使用到被代理类的地方都可以替换成代理类)
代码实现:
共同的接口类:
public interface FuncInterface {
public void work();
public void rest();
}
被代理类:(被代理类需要实现接口)
public class Tool implements FuncInterface{
@Override
public void work() {
System.out.println("Tool工作");
}
@Override
public void rest() {
System.out.println("Tool休息");
}
}
代理类:(也要实现接口)
public class ProxyObj implements FuncInterface{
private Tool tool;
public ProxyObj(FuncInterface obj) {
tool = (Tool)obj;
}
@Override
public void work() {
System.out.println("代理类在被代理对象work的时候可以选择做些事情...");
tool.work();
}
@Override
public void rest() {
System.out.println("代理类在被代理对象rest时候可以选择做些事情...");
tool.rest();
}
}
调用对象:(想去调用工具,通过代理)
public class OtherUser {
public static void main(String[] args) {
ProxyObj proxyObj = new ProxyObj(new Tool());
System.out.println("xxx希望使用Tool");
proxyObj.work();
System.out.println("xxx使用完Tool");
proxyObj.rest();
}
}
运行截图:
上面我们使用了静态代理,对Tool类进行了代理,我们调用Tool类对象的时候,通过调用代理类的相应方法就可以达成我们的目标。为什么叫静态代理呢?从代码中我们可以知道,我们的代理类只能对work()
和rest()
方法进行代理,假设我们在Tool添加了其他的方法,那新增的方法proxy对象就没法代理,去调用newFunc()
的话就会出现错误了,你就得手动把新的代理的方法添加进去。动态代理就可以不用手动添加代理方法,它自动会帮你生成代理(具体原理后面说)。
动态代理
上面我说到了静态代理的时候也说了,静态代理只能对当前的被代理类中的方法生效,要是被代理类添加了功能,那么我们的代理类对新方法就失效了,要是这时候用代理类去调用新的方法,可能会出错。这样操作起来就会很麻烦,这时候我们就可以使用动态代理。
原理:
为什么从原理开始讲,这是有原因的,因为动态代理并没有静态代理直观,学着学着可能就会陷入一知半解的状态。
动态代理其实原理也和静态代理原理差不多,创建动态代理的过程中会自动生成代理类,生成的代理类继承自Proxy类并实现了我们的接口(这个代理类不是我们自己写的,是调用过程中生成的),实现了相同的接口意味着我们可以用这个代理类去调用与被代理类相同的方法,动态代理调用方法之后不会像静态代理一样直接去调用被代理类,而是通过我们自己写的一个InvocationHandler实例去调用被代理类(可以在Proxy对象中使用getInvocation()方法获取到),然后我们的功能的补充及增强都是在这个InvocationHandler实例中完成。下面涉及到Java反射的知识,没有的话看起来可能会很费劲。
FuncInterFace:(共同实现的接口)
public interface FuncInterface {
public void work();
public void rest();
}
Tool类:
public class Tool implements FuncInterface{
@Override
public void work() {
System.out.println("Tool工作");
}
@Override
public void rest() {
System.out.println("Tool休息");
}
}
ProxyHandlerOfTool类:(就是我们需要传入的InvocationHandler,使用到反射,不会的可以去看下java反射的内容:http://t.csdn.cn/sDgEL)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyHandlerOfTool implements InvocationHandler {
private Object proxied;
public ProxyHandlerOfTool(Object proxied){
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method " + method.getName());
Object obj = method.invoke(proxied,args);
System.out.println("After method " + method.getName());
return obj;
}
}
在这里我们才去调用代理对象,而代理对象需要在构造的时候传进来,这里我们使用的反射来调用的被代理对象的方法:method.invoke(proxied,args)
,在这之前你可以写一些补充功能等等。
OtherUser:
import java.lang.reflect.Proxy;
public class OtherUser {
public static void main(String[] args) {
Tool tool = new Tool();
FuncInterface toolProxy = (FuncInterface) Proxy.newProxyInstance(tool.getClass().getClassLoader(),tool.getClass().getInterfaces(),new ProxyHandlerOfTool(tool));
toolProxy.work();
}
}
这里我们使用的Proxy类给我们提供的创建代理类的方法,要求我们把被代理类的类加载器传入,被代理类实现的接口传入,以及一个InvocationHandler,传入后他就会帮我们生成一个代理类 (在Proxy.newProxyInstance(...)
上面加上System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
如果没有生成的话,可以替换成
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
)就可以看见一个$proxy0.class文件(IDEA中),这就是我们代理类的字节码文件,可以看到里面的内容,你可以发现它实现了我们的公共接口,然后它并没有直接调用被代理类的方法,而是在调用父类的InvocationHandler的invoke
方法。这时候我们写的补充代码就生效了。
$Proxy0类
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import jdkProxy.FuncInterface;
public final class $Proxy0 extends Proxy implements FuncInterface {
private static final Method m0;
private static final Method m1;
private static final Method m2;
private static final Method m3;
private static final Method m4;
public $Proxy0(InvocationHandler param1) {
super(var1);
}
public final int hashCode() {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final boolean equals(Object var1) {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void rest() {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void work() {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("jdkProxy.FuncInterface").getMethod("rest");
m4 = Class.forName("jdkProxy.FuncInterface").getMethod("work");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
private static Lookup proxyClassLookup(Lookup var0) throws IllegalAccessException {
if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
return MethodHandles.lookup();
} else {
throw new IllegalAccessException(var0.toString());
}
}
}
CGLib代理
最后就是CGLib代理,上面两种方法都是基于被增强类实现某一接口的情况,如果被增强类未实现任何接口,那么上面两种代理方式都无法使用。这时候我们还有一种手段,那就是使用CGLib代理,使用这种方式,我们需要重写方法拦截器的intercept
方法,CGLib代理方式原理是生成被代理类的子类,这个代理类进行方法调用的时候会进入到方法拦截器,而增强的方法就在我们之前重写的intercept
方法中进行编写。
在进行该操作时需要去下载相关依赖包:
前三个选择3.3.1,后一个选择2.2.2(我不知道其他版本会不会出现问题,这里是没有问题的)
其次就是你在运行Enhancer enhancer = new Enhancer();
时,如果出现了以下的问题,请将jdk版本换回1.8
操作步骤:file – Project Structure – Project – SDK项(Edit) – 选择JDK8或者是JDK1.8
Intercept类:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Interceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Before method " + method.getName());
Object obj = methodProxy.invokeSuper(o,objects); // 这里使用的是fastClass机制,非反射调用
System.out.println("After method " + method.getName());
return null;
}
}
Tool类:
public class Tool {
public void work() {
System.out.println("Tool工作");
}
public void rest() {
System.out.println("Tool休息");
}
}
调用测试:
OhterUser类:
import net.sf.cglib.proxy.Enhancer;
public class OtherUser {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(Tool.class.getClassLoader());
enhancer.setSuperclass(Tool.class);
enhancer.setCallback(new Interceptor());
Tool tool = (Tool) enhancer.create();
tool.work();
tool.rest();
}
}
运行结果:
至于如何实现的,这和JDK代理有点相似,都是会自动生成一个代理类,但是CGlib代理会生成代理类和目标类的fastClass,fastClass会给对象方法建立对应的索引,通过索引去获取方法再进行调用,我们这里的invokeSuper
就是采用这种方式调用。具体过程就是:我们调用代理对象的方法会进入MethodInterceptor中,然后去调用我们重写的Intercept
方法,从而达到方法增强的效果。具体CGlib我了解也不是很深,发现错误的话可以纠正以下。至于之后的内容之后再来补充了。