“万恶之源”的动态代理
前言
最近准备整理一下框架学习的只是,发现不管是RPC框架,还是Spring的框架,Mybatis的框架都有很多地方地方用到了动态代理的方式,例如我们强大的Spring AOP、Mybatis中的接口方法查询都是用到了JDK动态代理,为了后期巩固知识的方便,我希望自己能从基础入手,真正理解框架。也要求自己以这样的方式来记录学习/复习过程。
一、代理模式
1.1什么是代理模式?
代理模式的定义:代理模式是指给某一个对象提供一个代理对象,并由代理对象来控制对原对象(被代理)的引用。引战的说法就是,婚介所来介绍对象🙂
1.3代理模式分类
如果按照代理创建的时期来分类的话,可以分成静态代理和动态代理。
- 静态代理:由程序员创建或者特定的工具自动生成的源码,程序运行之前,代理类的字节码文件已经创建好了。
- 动态代理:通过反射机制动态创建,在程序运行时才创建。
二、静态代理
实现静态代理有四个步骤:
- 定义业务接口
- 被代理的类来实现业务接口
- 定义代理类并实现业务接口
- 客户端调用
下面我们将按照这个步骤来实现一个静态代理,仿照AOP,我们来实现一个记录数据库插入数据的前后日志需求。
2.1定义业务接口
IService.java
package com.proxy.stat;
/**
* created with IntelliJ IDEA.
* packageName : com.proxy.stat
* author : wujw
* date : 2018/9/12 21:47
* version : 1.0.0
* description : 业务接口
*/
public interface IService {
/**
* 将数据库插入一条数据
* @param data data
*/
void insert(String data);
}
2.2被代理类实现业务接口
ServiceImpl.java
package com.proxy.stat;
/**
* created with IntelliJ IDEA.
* packageName : com.proxy.stat
* author : wujw
* date : 2018/9/12 21:50
* version : 1.0.0
* description : 被代理类实现业务接口
*/
public class ServiceImpl implements IService{
public void insert(String data) {
System.out.println("insert into database : "+ data);
}
}
2.3定义代理类并实现业务接口
ServiceProxy.java
package com.proxy.stat;
/**
* created with IntelliJ IDEA.
* packageName : com.proxy.stat
* author : wujw
* date : 2018/9/12 21:52
* version : 1.0.0
* description : 代理类并实现业务接口
*/
public class ServiceProxy implements IService{
//被代理对象
private IService iService;
//构造方式注入
public ServiceProxy(final IService iService) {
this.iService = iService;
}
public void insert(String data) {
System.out.println("ready to insert into database");
iService.insert(data);
System.out.println("insert into database success");
}
}
2.4客户端调用测试
package com.proxy.stat;
/**
* created with IntelliJ IDEA.
* packageName : com.proxy.stat
* author : wujw
* date : 2018/9/12 21:59
* version : 1.0.0
* description : 客户端调用
*/
public class StaticProxyGo {
public static void main(String[] args) {
IService iService = new ServiceImpl();
//将被代理对象注入到代理类中
ServiceProxy proxy = new ServiceProxy(iService);
proxy.insert("Apple凌晨会发布什么产品呢?");
}
}
结果::::::::::::::::::::::::
ready to insert into database
insert into database : Apple凌晨会发布什么产品呢?
insert into database success
Process finished with exit code 0
这样我们就实现了一个静态代理。但是静态代理中每出现一个服务都需要为其去创建一个代理类,工作量太大了。
三、动态代理(JDK)
使用JDK动态代理步骤
- 创建被代理的接口和类
- 创建
InvocationHandler
接口的实现类。在invoke
方法中实现代理逻辑 - 通过
Proxy
的静态方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
创建一个代理对象。 - 客户端调用
3.1创建被代理的接口和类
我们依旧使用上面提到的静态代理中的接口和类。
Iservice.java
和ServiceImpl
3.2创建InvcationHandler接口的实现类
package com.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* created with IntelliJ IDEA.
* packageName : com.proxy.dynamic
* author : wujw
* date : 2018/9/12 22:19
* version : 1.0.0
* description : TODO
*/
public class MyInvocationHandler implements InvocationHandler {
//被代理的对象,Object类型
private Object object;
public MyInvocationHandler(Object object){
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("ready to insert into database");
Object returnValue = method.invoke(object,args);
System.out.println("insert into database success");
return returnValue;
}
}
3.4客户端调用
package com.proxy.dynamic;
import com.proxy.IService;
import com.proxy.ServiceImpl;
import java.lang.reflect.Proxy;
/**
* created with IntelliJ IDEA.
* packageName : com.proxy.dynamic
* author : wujw
* date : 2018/9/12 22:23
* version : 1.0.0
* description : TODO
*/
public class DynamicProxyGo {
public static void main(String[] args) {
IService iService = new ServiceImpl();
MyInvocationHandler handler = new MyInvocationHandler(iService);
//第一个参数是指定代理类的类加载器(我们传入当前测试类的类加载器)
//第二个参数是代理类需要实现的接口(我们传入被代理类实现的接口,这样生成的代理类和被代理类就实现了相同的接口)
//第三个参数是invocation handler,用来处理方法的调用。这里传入我们自己实现的handler
IService proxyObject = (IService) Proxy.newProxyInstance(DynamicProxyGo.class.getClassLoader(),iService.getClass().getInterfaces(),handler);
proxyObject.insert("Apple 凌晨会发布什么产品呢?");
}
}
结果::::::::::::::::::::::::::::::::
ready to insert into database
insert into database : Apple 凌晨会发布什么产品呢?
insert into database success
Process finished with exit code 0
看到结果和静态代理一致,说明我们的实现是成功的。
JDK来实现动态代理需要实现类通过接口业务定义方法(可看需要的参数),那对于没有接口的类,那我们如果实现动态代理呢?这时候就需要CGLIB
了。CGLIB
采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLIB
动态代理均是实现Spring AOP
的基础。
四、动态代理(CGLIB)
4.1创建CGLIB代理类
package com.proxy.dynamic;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* created with IntelliJ IDEA.
* packageName : com.proxy.dynamic
* author : wujw
* date : 2018/9/12 22:35
* version : 1.0.0
* description : TODO
*/
public class CGBILProxy implements MethodInterceptor {
//被代理对象
private Object object;
public Object getInstance(final Object object){
this.object = object;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.object.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("ready to insert into database");
Object returnValue = methodProxy.invoke(object,objects);
System.out.println("insert into database success");
return returnValue;
}
}
4.2客户端调用
package com.proxy.dynamic;
import com.proxy.ServiceImpl;
/**
* created with IntelliJ IDEA.
* packageName : com.proxy.dynamic
* author : wujw
* date : 2018/9/12 22:46
* version : 1.0.0
* description : TODO
*/
public class CGlibProxyGo {
public static void main(String[] args) {
//这次我们不使用接口
ServiceImpl iService = new ServiceImpl();
CGBILProxy cgbilProxy = new CGBILProxy();
ServiceImpl service = (ServiceImpl) cgbilProxy.getInstance(iService);
service.insert("Apple 凌晨会发布什么产品呢?");
}
}
结果::::::::::::::::::::::::::::::::
ready to insert into database
insert into database : Apple 凌晨会发布什么产品呢?
insert into database success
自此,我们已经实现了三类代理方式,接下来我们将继续深究代理模式的内部代码及其常用的框架中是实现案例。
我们将在下一篇幅中记录讨论,这里感谢前辈们的案例,给了我很多的参考案例
作者:黑米面包派
同步更新: https://www.wujiwen.cn
欢迎一起交流进步