小时候很喜欢看小故事大道理,用通俗易懂的小故事去解释抽象难懂的大道理,并受到启发,了解真谛。今天我们就来试着讲一讲 代理。
故事前情提要:老师布置了一道作业,要求同学们做完了交给他。
静态代理
首先,我们先创建一个学生接口,同学们都需要写完交作业
public interface Student {
void handWork();
}
实现类表示作业写完了要交作业了
public class StudentImpl implements Student {
private String name;
public StudentImpl(String name){
this.name = name;
}
public void handWork() {
System.out.println(name + "交作业");
}
}
有个同学叫小王,写完了正常交作业
public static void main(String[] args) {
StudentImpl student = new StudentImpl("小王");
student.handWork();
}
平时也是这么做的,最终打印出来“小王交作业”。
可是有一天,小王忽然有事请假了,他的作业做完了,谁去帮他交呢。没办法,只能委托玩的最好的小伙伴,小李来交作业了,并告诉他,我把交作业的事情全权交于你处理了。你一定不要辜负我的信任呐。
于是乎,小李成了小王的代理
public class StudentProxy implements Student {
Student student;
public StudentProxy(StudentImpl impl){
this.student = impl;
}
public void handWork() {
student.handWork();
}
}
作业写完了,小王不在,小李被小王委以重任,他就帮小王交了作业
public class StaticProxyTest {
public static void main(String[] args) {
StudentImpl student = new StudentImpl("小王");
StudentProxy studentProxy = new StudentProxy(student);
studentProxy.handWork();
}
}
这就是静态代理,所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
小王已经告知了小李需要做什么(交作业),他们都是做作业交作业,倘若有一天小王被老是罚了只有他自己需要交作业,或者小王想喝奶茶,那如果按照上边的逻辑,小李也要受罚,也要被迫喝奶茶了。
JDK动态代理
这次作业是老师罚你自己写的,我才不写呢
public class StudentInvocationHandler implements InvocationHandler {
Object target;
public StudentInvocationHandler(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" + method.getName() + "方法");
Object invoke = method.invoke(target, args);
return invoke;
}
}
invoke方法可以实现代理逻辑,invoke方法的三个参数含义:
- proxy:代理对象,就是bind方法生成的对象。
- method:当前调度的方法
- args:调度方法的参数
当我们使用了代理对象调度方法后,它就会进入到invoke方法里面。
小李拿到小王的授权,表示什么都能帮你做,只可惜小王只有一个交作业的事情,其实什么喝奶茶,吃辣条我也可以代理
public class ProxyTest {
public static void main(String[] args) {
StudentImpl student = new StudentImpl("小王");
StudentInvocationHandler studentInvocationHandler = new StudentInvocationHandler(student);
Student studentProxy = (Student)Proxy.newProxyInstance(Student.class.getClassLoader(), new Class[]{Student.class}, studentInvocationHandler);
studentProxy.handWork();
//趁机喝小王一杯奶茶,也是可以的
// studentProxy.drink();
}
}
建立代理对象和真实对象的关系
Student studentProxy = (Student)Proxy.newProxyInstance(Student.class.getClassLoader(), new Class[]{Student.class}, studentInvocationHandler);
其中newProxyInstance方法包含了3个参数。
-
ClassLoader
:类加载器,我们采用了target本身的类加载器。 -
Class<?>[]
:是把生成的动态代理对象下挂在哪些接口下,这个写法是放在target实现的接口下。 -
InvocationHandler
:是定义实现方法逻辑的代理类,它必须实现InvocationHandler接口的invoke方法,它就是代理逻辑方法的现实方法。
最终打印结果:代理执行handWork方法
小王交作业
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。需要实现java.lang.reflect.InvocationHandler
的invoke方法。也需要提供接口方可用JDK动态代理
CGLIB动态代理
如果没有接口,那么JDK动态代理就会抛错JdkProxyExample
需要写一个接口Student,再写一个StudentImpl实现类,小王也很无奈,我没有接口,就一个类,但是我还要你代理我,帮我交作业
public class StudentCglib {
public void handWork(String name) {
System.out.println(name + "交作业");
}
}
小李也恭敬不如从命了,毕竟小王说,他请假回来要和我开黑
public class CglibProxy implements MethodInterceptor {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("调用真实对象前");
//CGLIB反射调用真实对象方法
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("调用真实对象后");
return result;
}
}
这里采用了CGLIB的加强者Enhander
,通过设置超类的方法(setSuperclass
),然后通过setCallBack
方法设置哪个类为它的代理类。其中,参数为this就意味着是当前对象,那就要求用this这个对象实现接口MethodInterceptor
的方法——intercept
,然后返回代理对象。
那么此时当前类的intercept
方法就是其代理逻辑方法,包含四个参数
proxy
代理对象method
方法args
方法参数methodProxy
方法代理
没有接口,我也可以代理
public class CglibTest {
public static void main(String[] args) {
//CGLIB enhancer增强类对象
Enhancer enhancer = new Enhancer();
//设置增强类型
enhancer.setSuperclass(StudentCglib.class);
//定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor方法
enhancer.setCallback(new CglibProxy());
//生成并返回代理对象
StudentCglib proxy = (StudentCglib)enhancer.create();
proxy.handWork("小王");
}
}
最终结果: 调用真实对象前
小王交作业
调用真实对象后
最终,小王小李幸福的生活在了一起
JDK动态代理和CGLIB字节码生成的区别?
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final