代理模式
简要介绍
代理,就是代理人
当A想找B做一件事,但是可能B在十万八千里之外,联系不上,但是C又能联系上B,而且A也能找到C,所以A找C办这件事,但真正做事的人是B,C只是个代理人
一句话:代理人可以控制客户端对其他对象的访问
代理种类:
- 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
- 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
哪里用到这个模式了?
- 使用过的一些中间件例如;
RPC
框架,在拿到jar包对接口的描述后,中间件会在服务启动的时候生成对应的代理类,当调用接口的时候,实际是通过代理类发出的socket信息进行通过。 - 另外像我们常用的
MyBatis
,基本是定义接口但是不需要写实现类,就可以对xml
或者自定义注解里的sql
语句进行
类图
在上图中,Client就是A,Proxy就是C,RealSubject就是B,也就是真正做事的人
例子
定义一个接口叫HelloService
,然后定义一个sayHello
方法,我们通过代理来调用这个方法,来实现在目标对象执行sayHello
方法的前后记录日志
HelloService
public interface HelloService {
/**
* say hello
*/
void say();
}
实现类(具体做事的人):HelloServiceImpl
public class HelloServiceImpl implements HelloService{
@Override
public void say() {
System.out.println("hello!");
}
}
代理:HelloServiceProxy
可以看到我们就是在这里直接调用真正做事的人执行方法,并且前后还可以加额外的东西
public class HelloServiceProxy implements HelloService{
private final HelloService target;
public HelloServiceProxy(HelloService target) {
this.target = target;
}
@Override
public void say() {
System.out.println("记录日志");
target.say();
System.out.println("清理数据");
}
}
客户端:Main
public class Main {
public static void main(String[] args) {
// 目标对象
HelloService target = new HelloServiceImpl();
// 代理对象
HelloServiceProxy helloServiceProxy = new HelloServiceProxy(target);
helloServiceProxy.say();
}
}
执行结果:
记录日志
hello!
清理数据
缺点
经过上面的介绍,我们可以发现,一个代理类只能为一个接口服务,平时开发是有N多个接口的,肯定会产生很多的代理类的,所以我们就会想,有没有可能一个代理类,可以完成所有代理的功能,所以就有了动态代理
动态代理
JDK
JDK
为我们提供了动态代理的支持,我们的代理类需要实现java.lang.reflect.InvocationHandler
接口并且调用java.lang.reflect.Proxy
类的newProxyInstance
方法创建一个代理实例,然后调用具体的方法,其实就是通过反射生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler
来处理
在上述代码基础上加入MyInvocationHandler
:
public class MyInvocationHandler implements InvocationHandler {
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 执行具体方法
* @param proxy 代理类
* @param method 目标具体的方法
* @param args 方法参数
* @return 方法返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 打印方法执行信息
System.out.println(target.getClass().getName() + "." + method.getName());
// 前置通知
System.out.println("记录日志");
Object result = method.invoke(target, args);
System.out.println("清理数据");
return result;
}
public Object getProxy() {
// target.getClass().getInterfaces() 这个是获取目标对象实现的接口,也就是jdk的动态代理的对象必须得实现至少一个接口才可以被代理
// 第三个参数是一个InvocationHandler,可以写到方法里,也可以以这种形式写下来,实现接口然后this
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
}
关于Proxy.newProxyInstance
方法参数的说明:
- 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
- 第一个参数指定产生代理对象的类加载器(就是要为谁加代理,就传谁的class),需要将其指定为和目标对象同一个类加载器
- 第二个参数要实现和目标对象一样的接口(可以看出jdk的动态代理,其实就是把我们手动创建和代理对象相同接口的代理类自动化了),所以只需要拿到目标对象的实现接口
- 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
下面使用一下这个动态代理,看看好使不:
public class Main {
public static void main(String[] args) {
// 目标对象
HelloService target = new HelloServiceImpl();
MyInvocationHandler invocationHandler = new MyInvocationHandler(target);
HelloService proxy = (HelloService) invocationHandler.getProxy();
proxy.say();
}
}
结果:
com.hc.basics.dynamicproxy.jdk.HelloServiceImpl.say
记录日志
hello!
清理数据
cglib
jdk动态代理的局限性:只能对实现了接口的类进行代理,对于没有实现接口的类无法代理
cglib对于没有实现接口的类也可以进行代理
这个是咋动态生成代理的呢?他利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
cglib
需要引入依赖:
<!-- cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
在上述基础,再加一个CglibProxyFactory
类:
public class CglibProxyFactory implements MethodInterceptor {
/**
* 工具类
*/
private final Enhancer enhancer = new Enhancer();
private final Object target;
public CglibProxyFactory(Object target) {
this.target = target;
}
public Object getProxy() {
// 设置要创建子类的父类
enhancer.setSuperclass(target.getClass());
// 设置回调
enhancer.setCallback(this);
// 通过字节码技术动态创建子类实例
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 打印方法执行信息
System.out.println(target.getClass().getName() + "." + method.getName());
// 前置通知
System.out.println("记录日志");
Object result = method.invoke(target, args);
System.out.println("清理数据");
return result;
}
}
测试:
public class Main {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
CglibProxyFactory helloServiceProxyFactory = new CglibProxyFactory(helloService);
HelloService helloServiceProxy = (HelloService) helloServiceProxyFactory.getProxy();
helloServiceProxy.say();
// 找一个没有实现接口的类进行代理,上面的helloService实现了HelloService接口
Student student = new Student();
CglibProxyFactory studentProxyFactory = new CglibProxyFactory(student);
Student studentProxy = (Student) studentProxyFactory.getProxy();
studentProxy.study();
}
}
com.hc.basics.dynamicproxy.jdk.HelloServiceImpl.say
记录日志
hello!
清理数据
com.hc.basics.dynamicproxy.cglib.Student.study
记录日志
student study!
清理数据